文章目录
- 一、初始化简介
- 二、C++ 中的初始化
- 2.1、内置类型初始化
- 2.2、STL 容器类型初始化
- 2.3、new 的初始化
- 24、结构体初始化
- 2.5、类的初始化
- 三、参考资料
一、初始化简介
C++ 中的两种数据类型:内置类型和类类型
- 内置类型:
char、bool、short、int、float、double、指针等 C++ 语言支持的最基础的数据类型 - 类类型:标准库以及我们自己定义的各种类、结构体、模板类等,如
MyClass、MyStruct、std::vector<T>、std::string、std::unique_ptr<T>..
- 内置类型:
初始化和赋值的区别:
- 初始化的概念:创建变量(为特定类型的变量申请存储空间)的同时赋予初始值;如果是类类型,将调用类的
构造函数 - 赋值的概念:
已创建的变量的值用另一个值替代,不创建新的变量;如果是类类型,将调用类的赋值运算符operator=()
inta=1;// 初始化a=2;// 赋值MyClass obj1;// 初始化,调用 MyClass() 构造函数MyClass obj2{42,"hello"};// 初始化,调用 MyClass(int, string) 构造函数obj1=obj2;// 赋值,调用 operator=(const MyClass&)- 初始化的概念:创建变量(为特定类型的变量申请存储空间)的同时赋予初始值;如果是类类型,将调用类的
显示初始化、隐式初始化与未初始化:
- 显示初始化和隐式初始化:无论何时只要类的对象被创建就会执行构造函数,通过显式调用构造函数进行初始化被称为
显式初始化,否则叫做隐式初始化 - 未初始化:未被用户指定初始值,那么这些变量会被执行默认初始化,默认值取决于变量类型和定义变量的位置
- 内置类型:全局(包括定义在任何函数之外、命名空间之内的)变量或局部静态变量
初始化为 0(这种情况也叫值初始化);局部非静态变量或类成员未定义(未初始化) - 类类型:由类的默认(无参)构造决定
- 内置类型:全局(包括定义在任何函数之外、命名空间之内的)变量或局部静态变量
#include<iostream>// Cat 提供两个构造函数classCat{public:intage;Cat()=default;// ✅ = default: 显式请求编译器生成默认构造函数explicitCat(inti):age(i){}};intmain(){Cat cat1;// 隐式初始化: 调用默认构造函数Catcat2(10);// 隐式初始化: 调用一个形参的构造函数Cat cat3=Cat();// 显式初始化: 调用默认构造函数Cat cat31{};// 优选:列表初始化Cat cat4=Cat(5);// 显式初始化: 调用一个形参的构造函数Cat cat41{5};// 优选:列表初始化// 构造函数还可以搭配 new 一起使用, 用于在堆上分配内存Cat*cat5=newCat();Cat*cat51=newCat{};// 优选:列表初始化Cat*cat6=newCat(3);Cat*cat61=newCat{3};// 优选:列表初始化return0;}- 显示初始化和隐式初始化:无论何时只要类的对象被创建就会执行构造函数,通过显式调用构造函数进行初始化被称为
列表初始化(
使用花括号 {} 形式的初始化):C++11 新标准将列表初始化应用于所有对象的初始化,但一般有如下编程习惯内置类型习惯于用等号 = 初始化(C/C++ 通用)类类型习惯用构造函数圆括号()调用默认初始化或者花括号{} 优选零初始化显式初始化vector、map 和 set等容器类习惯用列表{}初始化
// 1、列表初始化可以防止窄化转换longdoubleld=3.1415;inta{ld};// 无法编译,转换存在信息丢失的风险intb={ld};// 无法编译,转换存在信息丢失的风险inta{static_cast<int>(ld)};// OK,有意转换intc(ld);// 可以编译,但信息丢失intd=ld;// 可以编译,但信息丢失
二、C++ 中的初始化
- 堆内存使用:
动态大小、跨函数使用或者有可能会使用较大的结构体(离开作用域时不会自动释放),使用堆内存是更合适的选择(比如类)- 栈内存使用:如果结构体较小且作用域明确(当变量超出作用域时,内存自动回收),使用栈内存定义结构体变量会更简单且高效(比如简单结构体,使用
{}初始化即可)
2.1、内置类型初始化
- 使用
等号(=)初始化一个类变量执行的是拷贝初始化,编译器会把等号右侧的初始值拷贝到新创建的对象中去,不使用等号则执行的是直接初始化
inti1=0;// (1) 拷贝初始化,C/C++ 中通用,**首选**inti2={0};// (2) 拷贝初始化inti3{0};// (3) 直接初始化inti4(0);// (4) 直接初始化inti5;// (5)未初始化,如果是全局变量值为 0;局部变量为随机值2.2、STL 容器类型初始化
// 1、普通容器变量初始化std::string s1="hello";// (1) 拷贝初始化std::string s2={"hello"};// (2) 拷贝初始化,它不可用于构造函数初始值列表,而(3)可以std::string s3{"hello"};// (3) 列表直接初始化,**首选**,容器类习惯用列表初始化std::strings4("hello");// (4) 构造直接初始化std::string s5;// (5)调用默认构造函数,值为空字符串,std::basic_string<char>();std::string s6{};// (6)调用默认构造函数,值为空字符串,std::basic_string<char>{};// 2、数组容器变量初始化// vu32InputNum1 = std::vector<int, std::allocator<int> >(2, std::allocator<int>());std::vector<int>vu32InputNum1(2);// 2 个 int,初始化为 0// u32InputNum2 = std::vector<int, std::allocator<int> >();std::vector<int>vu32InputNum2;// 调用默认构造函数,被默认初始化为空的 std::vector,初始值是一个空容器,不包含任何元素std::vector<int>vu32InputNum3{8,9};// 2 个 int,初始化为 8, 9// vsInputNames1 = std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > >(2, std::allocator<std::basic_string<char> >());std::vector<std::string>vsInputNames1(2);// 2 个 string,初始化为 ""// vsInputNames2 = std::vector<std::basic_string<char>, std::allocator<std::basic_string<char> > >();std::vector<std::string>vsInputNames2;// 调用默认构造函数,被默认初始化为空的 std::vector,初始值是一个空容器,不包含任何元素std::vector<std::string>vsInputNames3{"hello","world"};// 2 个 string,初始化为 "hello", "world"2.3、new 的初始化
// 对于类类型,有无括号没区别string*ps1=newstring;// 默认初始化为空 stringstring*ps2=newstring();// 值初始化为空 stringstring*ps3=newstring{};// 值初始化为空 string, **首选**// 对于内置类型,有括号进行值初始化,没有括号的值未定义!int*pi1=newint;// 默认初始化,*pi1 值未定义!int*pi2=newint();// 值初始化,*pi2 为 0int*pi3=newint{};// 值初始化,*pi3 为 8int*pia1=newint[3];// 3个默认初始化的int: 值未定义int*pia2=newint[3]();// 3个值初始化的int: 值都为0,需区分 () 是调用还是初始化,不推荐/****** 重要 **首选** *******/int*pia3=newint[3]{};// 3个值初始化的int: 值都为0,用于开辟一块内存并初始化为 0;类似 calloc,不用 memset 了constint*pci1=newconstint(1024);// 分配并初始化一个 const intconstint*pci2=newconstint{1024};// 分配并初始化一个 const int24、结构体初始化
// 重要:结构体类型使用列表初始化分配内存,可避免未初始化的成员变量带来的潜在问题typedefstructtagTAlgTensorHandle{ints32InputNum;intanInputWidth[2];intanInputHeight[2];std::string sInputName;std::vector<std::string>vsBrand;// 用于保存 brand 文件中的内容std::vector<std::string>vsBrand2Type;// 用于保存 brand 映射到 Type 文件中的内容void*pvDetectInGpuBuffer;// 模型输入,按照检测模型的输入大小分配void*pbyInputDataGpu;// 传入大图+ROI时,放置抠出小图的缓存}TAlgTensorHandle;// 可以默认初始值初始化一部分成员变量typedefstructtagTAlgTensorHandle{ints32InputNum{8};intanInputWidth[2]{112,112};intanInputHeight[2]{224,224};std::string sInputName;std::vector<std::string>vsBrand;// 用于保存 brand 文件中的内容std::vector<std::string>vsBrand2Type;// 用于保存 brand 映射到 Type 文件中的内容void*pvDetectInGpuBuffer;// 模型输入,按照检测模型的输入大小分配void*pbyInputDataGpu;// 传入大图+ROI时,放置抠出小图的缓存}TAlgTensorHandle;// 1、普通初始化:成员变量未定义TAlgTensorHandle ptTensorContext1;// 2、列表初始化:成员变量初始化为 0,nullptr 或空/********** 对于结构体类型优先使用此方式(不需要开辟内存的时候)进行初始化 *****************/TAlgTensorHandle ptTensorContext2{};// 3、开辟内存:将分配内存并创建一个 TAlgTensorHandle 对象,但不会对对象进行任何初始化。对象的成员变量将保持未定义的状态TAlgTensorHandle*ptTensorContext1=newTAlgTensorHandle;// 4、开辟内存将分配内存并创建一个 TAlgTensorHandle 对象,同时将所有成员变量初始化为其默认值。// 对于基本类型(如 int),它们会被初始化为 0;对于 std::string 和 std::vector,它们会被初始化为空/********** 对于结构体类型优先使用此方式(或者 C 语言 calloc 的方式)进行初始化 *****************/TAlgTensorHandle*ptTensorContext2=newTAlgTensorHandle{};printf("sizeof handle is %d %d\n",sizeof(*ptTensorContext2),sizeof(TAlgTensorHandle));2.5、类的初始化
类成员有两种初始化方式:类内初始值(
成员初始化器,in-class member initializer)以及构造函数初始值列表(constructor initialize list)- 尽量不要在构造函数体内部初始化数据成员,因为只有当类的所有成员初始化完成之后才开始执行构造函数体,此时并不是真正意义上的初始化,而是
重新赋值 引用成员、const 成员只能通过类内初始值或者构造函数初始值列表初始化,而不能在构造函数体内部“初始化”- 注意:
- 对于内置类型的数据成员,如果没有对其进行显式初始化,
其值未定义 - 类的数据成员初始化顺序和构造函数初始化列表中的顺序无关,而是由
成员在类中声明的顺序决定
- 对于内置类型的数据成员,如果没有对其进行显式初始化,
- 尽量不要在构造函数体内部初始化数据成员,因为只有当类的所有成员初始化完成之后才开始执行构造函数体,此时并不是真正意义上的初始化,而是
类内初始值/成员初始化器:
- 在类中声明类的(
非静态)数据成员同时提供初始值,初始值可以是字面值、表达式甚至是函数调用 - 形式上可以用等号或者花括号,但是
不能用圆括号,C++11 之后首选的初始化类成员方式
classSalesData{unsignedunitsSold=0;doublerevenue{0.0};std::string bookNo{"hello"};shared_ptr<int>sp={make_shared<int>(5)};};- 在类中声明类的(
构造函数初始值列表:
- 如果需要根据
传入构造函数的参数来初始化类成员,可以使用构造函数初始值列表 - 构造函数初始值列表的形式是在构造函数的形参列表之后,
使用冒号分隔,接着是成员名字,然后使用圆括号或花括号来包裹初始化的表达式,多个成员之间通过逗号分隔
- 如果需要根据
classSalesData{public:SalesData(conststd::string&s):bookNo(s){}SalesData(conststd::string&s,unsignedn,doublep):bookNo(s),unitsSold(n),revenue(p*n){}};- 类内初始值和构造函数初始化列表同时使用:一部分成员变量使用类内初始默认值,一部分成员变量通过传参使用列表初始化
classKNN{public:KNN(constchar*name):sInputName(name){};// 构造函数public:ints32InputNum;ints32OutPutNum{1};intanInputWidth[2]{112,112};intanInputHeight[2]{224,224};std::string sInputName;void*pvPltParamHandle;char*u8Algname;private:ints32Modelsize;std::string key;};// 1、这种方式初始化未构造的参数,可能是未知的KNN pcAlgHandle1{"det"};// KNN pcAlgHandle1("det");// 2、这种方式初始化,成员变量一般会初始化为 0 或 nullptr 等KNN*pcAlgHandle2=newKNN{"cls"};// KNN *pcAlgHandle2 = new KNN("cls");// 除了需要外部传入的参数外,其它参数可以先使用默认初始化参数(或者不初始化),后面再赋值改变(一定要做)classKNN{public:KNN(constchar*name):sInputName(name){};// 构造函数public:ints32InputNum{1};ints32OutPutNum{1};intanInputWidth[2]{112,112};intanInputHeight[2]{224,224};std::string sInputName{"input"};void*pvPltParamHandle;char*u8Algname;private:ints32Modelsize{0};std::string key;};三、参考资料
1、一文总结现代 C++ 中的初始化
2、一文讲通 C++ 所有初始化方法
3、探索C++中的“{}初始化”:优雅与高效的结合
4、C++中的初始化列表
5、五花八门的C++初始化规则
6、https://cppinsights.io