Last Updated: 2023-12-27 08:12:56 Wednesday
-- TOC --
C++为了兼容C,在对象(变量就是有名称的对象)初始化的语法上,可谓花样百出,眼花缭乱呀。。。
也有一些书中将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在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;
}
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
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
不定义任何Constructor,就是与C一致的风格!只要显示定义任意Constructor,此风格不在...
xxx()=default
,此时它只是个zero-param constructor,只是拥有了{}
清零的功能,真正的与C语言一致的多样化的结构体初始化能力不再拥有。={}
能够将所有成员变量清零,这是default constructor在发挥作用,如果定义一个zero-param constructor,没有=default
,{}
将不在有清零的功能,只是在调用这个zero-param constructor,除非这个ctor自己实现了清零。xxx()=default
的定义显然可以与其它constructor的定义并存,因为它只是个zero-param constructor,只是拥有了{}
清零的功能。{}
初始化
Braced Initialization
, which is a safe explicit type conversion.
{}
进行对象(包括变量)的初始化的时候,编译器会增加一层保护,比如当出现所谓的narrow conversion
的时候,会有编译错误。而C语言,是没有这些的,C编译器认为程序员知道自己在干什么...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++代码,不是C代码。因此,最后总结一下,使用C++,尽量用{}
来做初始化!我喜欢{}前面不带=号,这样看起来就像是初始化一个fully featured对象,很C++!
本文链接:https://cs.pynote.net/sf/c/cpp/202208221/
-- EOF --
-- MORE --