C++对象的花式初始化

Last Updated: 2023-12-27 08:12:56 Wednesday

-- TOC --

C++为了兼容C,在对象(变量就是有名称的对象)初始化的语法上,可谓花样百出,眼花缭乱呀。。。

初始化Fundamental Type

也有一些书中将fundamental type说成primitive type或builtin type。

C语言中的所有类型,在C++的语境内,都是Fundamental Type。因此,初始化这些类型,使用C语法是OK的:

int a = 0;  // Initialized to 0, C style
int b {};   // Initialized to 0
int c = {}; // Initialized to 0
int d;      // No Initialization, ramdom value

大括号{}初始化,是C++11引入的新语法,推荐使用。

int e = 42;      // Initialized to 42
int f { 42 };    // Initialized to 42
int g = { 42 };  // Initialized to 42
int h(42);       // Initialized to 42,不建议使用小括号,能看懂老代码就行

小括号也是C++支持的语法,不过,现在已经不推荐使用了,使用小括号,看起来很像函数申明或调用,因此不推荐!

初始化struct结构体

struct在C++中,已经是跟class一个级别的选手了。没有成员函数的struct,连同C语言中的其它类型,统称为POD(Plain Old Data)。The simplest kind of classes are plain-old-data classes (PODs). PODs(struct) are simple containers. Think of them as a sort of heterogeneous array of elements of potentially different types. Each element of a class is called a member.

#include <iostream>
using namespace std;

struct PodStruct {
    uint64_t a;
    char b[256];
    bool c;
    const int g {1};  //!!
    void show(){
        cout << "a:" << a << ", ";
        cout << "b:" << b << ", ";
        cout << "c:" << c << ", ";
        cout << "g:" << g << ", ";
        cout << endl;
    }
};

int main() {
    // No Initialization, ramdom value, except g
    PodStruct pod1;
    pod1.show();
    // All fields zeroed, except g
    PodStruct pod2 {};
    pod2.show();
    // All fields zeroed, C style, except g
    PodStruct pod3 = {};
    pod3.show();
    // Fields a & b are set; c is set to zero by compiler, g = 1
    PodStruct pod4 {42, "Hello"};
    pod4.show();
    // All fields are set, g = 2
    PodStruct pod5 {42, "Hello", true, 2};
    pod5.show();
}

输出:

a:139939222286312, b:�\�#F, c:0, g:1, 
a:0, b:, c:0, g:1, 
a:0, b:, c:0, g:1, 
a:42, b:Hello, c:0, g:1, 
a:42, b:Hello, c:1, g:2, 

等等~~!POD没有成员函数,上面的示例怎么有?

是的。如果把show函数去掉,结果完全一样。这个示例说明,struct只要不定义constructor,初始化就与是否有成员函数无关!不定义constructor,就是default行为,如果成员变量的数量很多,也许反而非常方便,灵活的初始化,无需写很多overload的constructor!

特别注意变量g!合法的对象初始化,可以覆盖预先定义的const变量的值,class type对象的const成员变量依然如此,参考C++的初始化列表

以上初始化struct的方法并不是全部,C语言还有一种tagged structure initialization,在Linux内核代码中很普遍,具体请参考C语言struct结构体的初始化

再看一个示例,说明不定义constructor的好处:

#include <iostream>
using namespace std;

struct you {
    int a;
    int b;
    int c;
    int d;
    void show(){
        cout << "a:" << a << ", ";
        cout << "b:" << b << ", ";
        cout << "c:" << c << ", ";
        cout << "d:" << d << ", ";
        cout << endl;
    }
};

int main() {
    you y1 {};
    y1.show();
    you y2 {1,2,3,4};
    y2.show();
    you y3 {
        .b = 12,
        .d = 19,
    };
    y3.show();
}

输出:

a:0, b:0, c:0, d:0, 
a:1, b:2, c:3, d:4, 
a:0, b:12, c:0, d:19,

试试用其它对象来初始化struct,测试代码如下:

#include <iostream>
using namespace std;

struct aaa{
    int a;
    int b;
};

struct xyz{
    int a;
    int b;
    aaa c;
};

int main(void) {
    int a {1};
    char b {2};
    xyz x {a,b,{a,b}};
    cout << x.a << x.b << x.c.a << x.c.b << endl;
    return 0;
}

继续学习:Structured Binding in C++

初始化array数组

int main() {
    int array_1[]{ 1, 2, 3 };  // Array of length 3; 1, 2, 3
    int array_2[5]{};          // Array of length 5; 0, 0, 0, 0, 0
    int array_3[5]{ 1, 2, 3 }; // Array of length 5; 1, 2, 3, 0, 0
    int array_4[5];            // Array of length 5; uninitialized values
    int array_5[] = {1,2,3};   // C style, length 3; 1, 2, 3
    int array_6[5] = {1,2,3};  // C style, 1, 2, 3, 0, 0
    int array_7[5] = {0};      // C style, set first 0, others are set 0 by compiler
    int array_8[5] = {};       // C style, all 0
}

C style都要有=号!

补充一组C++ style数组定义(指针一定有new):

int *a{ new int[24]{} };  // all zero
int *a{ new int[24]{5} }; // only the first is 5, the others are all zero 

初始化class对象

C++是对象视角,fundamental types也可被看成object,但是这类object的初始化是编译器直接进行的,而fully featured对象的初始化,就需要用到constructor了!这个升级很自然,也没啥不对的。

请看下面这段测试代码:

$ cat test_init.cpp
#include <cstdio>

struct mono {
    mono() {
        printf("no argument\n");
    }
    mono(int a) {
        printf("int: %d\n", a);
    }
    mono(char b) {
        printf("char: %c\n", b);
    }
    mono(float c) {
        printf("float: %f\n", c);
    }
};

int main(void) {
    mono m1;  // call zero-parameter constructor.
    mono m2{};
    mono m3{123};
    mono m4{'c'};
    mono m5{1.234f};
    mono m6();  // warning, no output
    return 0;
}

这段代码编译时,有个warning,运行时,m6的初始化没有任何输出。

$ g++ test_init.cpp
test_init.cpp: In function ‘int main()’:
test_init.cpp:26:12: warning: empty parentheses were disambiguated as a function declaration [-Wvexing-parse]
   26 |     mono m6();
      |            ^~
test_init.cpp:26:12: note: remove parentheses to default-initialize a variable
   26 |     mono m6();
      |            ^~
      |            --
test_init.cpp:26:12: note: or replace parentheses with braces to value-initialize a variable
$ ./a.out
no argument
no argument
int: 123
char: c
float: 1.234000
$

还是重复上面说过的,不要使用小括号初始化!这是个错误!上面m6这一行代码,会被编译器当做一个函数申明,因此没有调用mono的constructor,没有输出。

隐式对象初始化

这是不推荐使用的方法,如下:

mono m7 = 678;
mono m8 = 'g';
mono m9 = 1.234f;

上面三行代码是合法的,但这种方式很容易出现难以定位的bug,这部分内容,具体请参考:用explicit申明单参数construcor

Default Constructor

不定义任何Constructor,就是与C一致的风格!只要显示定义任意Constructor,此风格不在...

为什么要使用{}初始化

Braced Initialization, which is a safe explicit type conversion.

narrow conversion,就是会损失信息的类型转换,比如float转int,double转float。

$ cat test_init.cpp
#include <cstdio>

int main(void) {
    int a{5.0/3};
    printf("%d\n", a);
    return 0;
}

C++编译器会生产一个error:

$ g++ -Wall -Wextra test_init.cpp -o test_init
test_init.cpp: In function ‘int main()’:
test_init.cpp:5:14: error: narrowing conversion of ‘1.6666666666666667e+0’ from ‘double’ to ‘int’ [-Wnarrowing]
    5 |     int a{5.0/3};

而C编译就不会:

$ cat test.c
#include <stdio.h>

int main(void) {
    int a = 5.0/3;
    printf("%d\n", a);
    return 0;
}
$ gcc -Wall -Wextra test.c -o test
$ ./test
1

The use of braced initialization ensures at compile time that only safe, well-behaved, non-narrowing conversions are allowed.

因此,最后总结一下,使用C++,尽量用{}来做初始化!我喜欢{}前面不带=号,这样看起来就像是初始化一个fully featured对象,很C++!

本文链接:https://cs.pynote.net/sf/c/cpp/202208221/

-- EOF --

-- MORE --