开发者所做的测试。
可控制性,可观测性,可定位性。
可定位性:不管故障被自愈,容错,还是最终失败,异常都需要输出,不能被内部逻辑完全消化。
日志要分层(INFO/WARN/ERROR),日志量与可定位性要权衡,太少不足以定位问题,太多影响性能,浪费磁盘空间。
有时过程中的交互消息/事件、状态、性能、资源等过程信息输出对问题定位也有非常关键的作用。
测试分层:UT:IT:ST = 7:2:1
单元:最小粒度的功能单元/工作单元。
单元测试:一段自动化的代码。
单元测试(Unit Testing)是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。
一个单元测试是一段自动化的代码,这段代码调用被测试的工作单元,之后对这个单元的单个最终结果的某些假设进行校验。单元测试几乎都是用单元测试框架编写的;只要产品代码不发生变化,单元测试的结果是稳定的。
CUnit
测试框架gtest/gmock
https://github.com/google/googletest/tree/main/googletest/samples
https://github.com/google/googletest/tree/main/googlemock/test
DECLARE_FUNCTION_MOCK
IMPLEMENT_FUNCTION_MOCK
EXPECT_FUNCTION_CALL
(WillOnce,WillRepeatedly,Invoke)
https://www.cnblogs.com/welkinwalker/archive/2011/11/29/2267225.html
https://www.cnblogs.com/coderzh/archive/2009/04/06/1426755.html
gtest的官方网站是: http://code.google.com/p/googletest/
测试框架关键字简介:
1.TEST_F宏:每个宏定义一个测试用例,宏的两个参数分别代表测试套名和测试用例名
2.SetUp和TearDown: 在每个测试用例调用前和调用后执行
3.SetUpTestCase和TearDownTestCase:在测试套调用前和调用后执行,必须为static void类型
4.EXPECT_EQ:预期结果检查
5.MOCK_METHOD1,EXPECT_CALL:对一个类的接口进行打桩/模拟
单元测试原则FIRST:
F-FAST(快速原则)
I-Independent(独立原则)
R-Repeatable(可重复原则)
S-Self Validating(自我验证原则)
T-Timely(及时原则)
单元测试用例需要符合BCDE原则:
B:Border 边界值测试
C:Correct 正确的输入
D:Design 与设计文档相结合
E:Error 强制的错误输入
单元测试的作用: 1.单元测试描述了代码的预期行为,可以最有效地保证代码正确运行 2.由于单元规模较小,当因为代码变更出现问题的时候,可以帮助我们快速定位问题 3.有单元测试覆盖的代码,让我们更有信心,敢于放心做代码重构
集成测试也叫组装测试或联合测试,按照设计要求将各个单元,组件或子系统集成到一起测试是否能正确运行。
系统测试是对整个系统的测试,将硬件,软件,操作人员看作一个整体,检验它是否有不符合系统说明书的地方。
TDD = TFD + Refactoring 即测试先行加上重构。
测试驱动开发是一种由一系列短小迭代组成的软件开发技术。
测试对象概述
(单选题)1.软件测试是软件质量保证的重要手段,( )是软件测试中的最基础环节
2.关于软件测试,以下说法错误的是( )
(多选题)1.关于可测试性,以下描述正确的是:( )
2.关于单元测试,以下说法正确的是:( )
3.关于测试分层,以下说法正确的是:( )
• C/C++单元测试框架gtest
• Java单元测试框架JUnit
• Python单元测试框架PyUnit
• go单元测试框架可以用go自带的gotest包
相关书籍
• 《软件测试基础教程(第2版)》
• 《有效的单元测试》
• 《JUnit Recipes-程序员使用测试技巧》(JAVA)
• 《软件测试》
• 《Clean Code》
• 《重构—改善既有代码的设计》
• 《xUnit测试模式–测试码重构 》
• 《单元测试之道》
• 《实战Infusion 代码坏味道浅析》(JAVA)
相关链接
• 白盒测试覆盖技术:https://blog.csdn.net/virus2014/article/details/51217026
• JUnit4(JAVA):https://github.com/junit-team/junit4/wiki
• Mockito(JAVA):http://static.javadoc.io/org.mockito/mockito-core/2.25.1/org/mockito/Mockito.html
• 《Debugging with GDB》:https://www.gnu.org/software/gdb/documentation/
strlen功能:计算给定字符串的长度,不包括‘\0’
strlen是一个函数,参数是char *
sizeof是关键字,取字节运算符
sizeof功能:返回一个对象或者类型所占的内存字节数
str.length()和str.size() 是求string类型对象的成员函数
#include <cstring>
#include <iostream>
#include <stdio.h>
using namespace std;
#define test "test string";
int main() {
char *s = "test string"; //warning: deprecated conversion from string constant to ‘char*’
cout << "strlen(s)=" << strlen(s) << endl; //strlen(s)=11
cout << "sizeof(s)=" << sizeof(s) << endl; //sizeof(s)=8
cout << endl;
char mystr[100] = "test string";
cout << "strlen(mystr)=" << strlen(mystr) << endl; //strlen(mystr)=11
cout << "sizeof(mystr)=" << sizeof(mystr) << endl; //sizeof(mystr)=100
cout << endl;
char ss[] = "test string";
cout << "strlen(ss)=" << strlen(ss) << endl; //strlen(ss)=11
cout << "sizeof(ss)=" << sizeof(ss) << endl; //sizeof(ss)=12
cout << endl;
char *p = mystr;
cout << "strlen(p)=" << strlen(p) << endl; //strlen(p)=11
cout << "sizeof(p)=" << sizeof(p) << endl; //sizeof(p)=8
cout << endl;
string cur = "test string";
cout << "strlen(cur)=" << strlen(cur.c_str()) << endl; //strlen(cur)=11
cout << "cur.size()=" << cur.size() << endl; //cur.size()=11
cout << "cur.length()=" << cur.length() << endl; //cur.length()=11
cout << "sizeof(cur)=" << sizeof(cur) << endl; //sizeof(cur)=8
cout << endl;
string now = test;
cout << "strlen(now)=" << strlen(now.c_str()) << endl;//strlen(now)=11
cout << "now.size()=" << now.size() << endl;//now.size()=11
cout << "now.length()=" << now.length() << endl;//now.length()=11
cout << "sizeof(now)=" << sizeof(now) << endl;//sizeof(now)=8
cout << endl;
return 0;
}
运行结果:
strlen函数求得的字符串长度是从字符串第一个元素到第一个’\0’之间元素的个数(如果字符串中间有’\0’,则结果不是整个字符串的长度),同时不包括该’\0’
sizeof求得的结果是存储该字符串的变量占用的空间大小,因而一定会包括’\0’.若’\0’后还有空余的空间,也会包含到结果里面
char * strcpy ( char * destination, const char * source );
strcpy 是依据 “\0” 作为结束判断的,如果 destination 的空间不够,则会引起 buffer overflow。
char str1[100];
char *s = "test string";
cout << "strlen(s)=" << strlen(s) << endl; //strlen(s)=11
cout << "sizeof(s)=" << sizeof(s) << endl; //sizeof(s)=8
strcpy(str1, s);
cout << "strcpy str1:" << str1 << endl; //strcpy str1:test string
cout << "strlen(str1)=" << strlen(str1) << endl; //strlen(str1)=11
cout << "sizeof(str1)=" << sizeof(str1) << endl; //sizeof(str1)=100
cout << endl;
char * strncpy ( char * destination, const char * source, size_t num );
strncpy要安全一些。
拷贝你指定的个数或者碰到’\0’,不验证源缓冲区长度,可能造成越界。
如果遇到空字符(’\0’),则空字符后面为原来的字符。
char str2[100];
char str3[10];
char mystr[100] = "test string";
cout << "strlen(mystr)=" << strlen(mystr) << endl;//strlen(mystr)=11
cout << "sizeof(mystr)=" << sizeof(mystr) << endl;//sizeof(mystr)=100
strncpy(str2, mystr, sizeof(str2));
cout << "strncpy str2:" << str2 << endl;//strncpy str2:test string
cout << "strlen(str2)=" << strlen(str2) << endl; //strlen(str2)=11
cout << "sizeof(str2)=" << sizeof(str2) << endl; //sizeof(str2)=100
strncpy(str3, mystr, 4);
str3[4] = '\0';
cout << "strncpy str3:" << str3 << endl; //strncpy str3:test
cout << "strlen(str3)=" << strlen(str3) << endl;//strlen(str3)=4
cout << "sizeof(str3)=" << sizeof(str3) << endl;//sizeof(str3)=10
cout << endl;
编译选项
g++ main.cpp -std=c++11 /home/lzy/code/taurus-root/src/3rdparty/securec/lib/libsecurec.a
头文件:#include “securec.h”
strncpy_s
不以零填充目标数组。这是转换既存代码到边界检查版本的常见错误源。
dest | - | 指向要复制到的字符数组的指针 |
---|---|---|
src | - | 指向复制来源的字符数组的指针 |
count | - | 要复制的最大字符数 |
destsz | - | 目标缓冲区的大小 |
errno_t strncpy_s(char *restrict dest, rsize_t destsz,
const char *restrict src, rsize_t count);
char str4[100];
char str5[10];
char ss[] = "test string";
cout << "strlen(ss)=" << strlen(ss) << endl; //strlen(ss)=11
cout << "sizeof(ss)=" << sizeof(ss) << endl; //sizeof(ss)=12
strncpy_s(str4, sizeof(str4), ss, sizeof(ss));
cout << "strncpy_s str4:" << str4 << endl; //strncpy_s str4:test string
cout << "strlen(str4)=" << strlen(str4) << endl; //strlen(str4)=11
cout << "sizeof(str4)=" << sizeof(str4) << endl; //sizeof(str4)=100
strncpy_s(str5, 10, ss, 4);
str5[4] = '\0';
cout << "strncpy_s str5:" << str5 << endl; //strncpy_s str5:test
cout << "strlen(str5)=" << strlen(str5) << endl; //strlen(str5)=4
cout << "sizeof(str5)=" << sizeof(str5) << endl; //sizeof(str5)=10
cout << endl;
memcpy:不理’\0’,只拷贝你指定的个数,故strcpy可以不指定字符串长度,实现整串copy,而memcpy必定要指定长度。
void * memcpy(void * dest,const void * src,size_t count);
char str6[100];
char str7[4];
char *p = mystr;
cout << "strlen(p)=" << strlen(p) << endl; //strlen(p)=11
cout << "sizeof(p)=" << sizeof(p) << endl; //sizeof(p)=8
memcpy(str6, p, strlen(p) + 1);
cout << "memcpy str6:" << str6 << endl; //memcpy str6:test string
cout << "strlen(str6)=" << strlen(str6) << endl; //strlen(str6)=11
cout << "sizeof(str6)=" << sizeof(str6) << endl; //sizeof(str6)=100
memcpy(str7, p, 4);
cout << "memcpy str7:" << str7 << endl; //memcpy str7:test
cout << "strlen(str7)=" << strlen(str7) << endl; //strlen(str7)=4
cout << "sizeof(str7)=" << sizeof(str7) << endl; //sizeof(str7)=4
cout << endl;
count
大于 destsz
(会发生缓冲区溢出)
dest | - | 指向要复制的对象的指针 |
---|---|---|
destsz | - | 目标中要修改的最大字节数(典型地为目标对象的大小) |
src | - | 指向复制来源对象的指针 |
count | - | 复制的字节数 |
errno_t memcpy_s( void *restrict dest, rsize_t destsz,
const void *restrict src, rsize_t count );
char str8[100];
char str9[4];
char str10[4];
string cur = "test string";
string cur2 = "ab";
cout << "strlen(cur)=" << strlen(cur.c_str()) << endl;//strlen(cur)=11
cout << "cur.size()=" << cur.size() << endl; //cur.size()=11
cout << "cur.length()=" << cur.length() << endl; //cur.length()=11
cout << "sizeof(cur)=" << sizeof(cur) << endl; //sizeof(cur)=8
memcpy_s(str8, sizeof(str8), cur.c_str(), strlen(cur.c_str()) + 1);
cout << "memcpy_s str8:" << str8 << endl; //memcpy_s str8:test string
cout << "strlen(str8)=" << strlen(str8) << endl; //strlen(str8)=11
cout << "sizeof(str8)=" << sizeof(str8) << endl; //sizeof(str8)=100
memcpy_s(str9, 4, cur.c_str(), 4);
cout << "memcpy_s str9:" << str9 << endl; //memcpy_s str9:test
cout << "strlen(str9)=" << strlen(str9) << endl; //strlen(str9)=4
cout << "sizeof(str9)=" << sizeof(str9) << endl; //sizeof(str9)=4
memcpy_s(str10, 4, cur.c_str(), 3);
cout << "memcpy_s str10:" << str10 << endl; //memcpy_s str10:tes
cout << "strlen(str10)=" << strlen(str10) << endl; //strlen(str10)=3
cout << "sizeof(str10)=" << sizeof(str10) << endl; //sizeof(str10)=4
cout << endl;
a.下载所需要的rpm包,gcc需要安装比较多的rpm包,而且不同rpm包之间具有依赖关系,需要按照一定顺序安装,这里推荐直接下载附件;
b.把下载好的附件解压,复制到Linux环境下,例如复制到~/home文件夹下;
c.执行 cd /home/gcc-4.8.5 ,进入该目录下,执行 rpm -ivh –force *,等待安装完成即可;
d.执行 gcc –version,出现版本号说明gcc安装成功。
g++ test.cpp --verbose -I/home/lzy
https://bbs.huaweicloud.com/blogs/308450
https://colobu.com/2018/08/28/15-Most-Frequently-Used-GCC-Compiler-Command-Line-Options/
生成一个a.out文件
gcc main.c
使用参数-o, 可以指定输出的文件名。
gcc main.c -o main
gcc -Wall main.c -o main
gcc -E main.c > main.i
gcc命令将结果输出在stdout中,所以你可以把它重定向到任意的文件中,在上面的例子中,重定向到main.i文件中。
gcc -S main.c > main.s
文件main.s包含汇编代码。
gcc -C main.c
代码产生main.o, 包含机器级别的代码或者编译的代码。
-C参数只产生编译的代码(没有链接link)。
$ gcc -save-temps main.c
$ ls
a.out main.c main.i main.o main.s
gcc -Wall main.c -o main -lCPPfile
上面的代码会链接libCPPfile.so,产生可执行文件main。
pthread库不是Linux系统默认的库,连接时需要使用库libpthread.a,所以在使用pthread_create创建线程时,在编译中要加-lpthread参数:
gcc -o test -lpthread test.c
$ gcc -c -Wall -Werror -fPIC Cfile.c
$ gcc -shared -o libCfile.so Cfile.o
产生共享库的时候使用了-fPIC参数。
注意-shared产生共享库
$ gcc -Wall -v main.c -o main
Using built-in specs.
COLLECT_GCC=gcc
……
ISO C89不支持C++风格的注释
$ gcc -Wall -funsigned-char main.c -o main
$ ./main
The Geek Stuff [246]
通过这个参数, char类型被看作为 unsigned char类型
$ gcc -Wall -fsigned-char main.c -o main
$ ./main
The Geek Stuff [-10]
使用这个参数, char类型被看作是有符号的
$ gcc -Wall -DMY_MACRO main.c -o main
$ ./main
可以看到宏MY_MACRO被定义了,并打印出了结果。
$ gcc -Wall -Werror main.c -o main
$ cat opt_file
-Wall -omain
$ gcc main.c @opt_file
gcc -I/home/codeman/include input-file.c
gcc -std=c++11 hello-world.cpp
标准如 c++11, c++14, c90, c89等。
g++ -g -Wall -std=c++11 main.cpp
gcc main.c -static -o main -lpthread
使用-shared使用动态库链接。
如果没有使用-static,默认使用libstdc++共享库,而-static-libstdc++可以指定使用libstdc++静态库。
gcc -M main.c
http://www.runoob.com/w3cnote/gcc-parameter-detail.html
gcc参数详解
选项 | 解释 |
---|---|
-ansi | 只支持 ANSI 标准的 C 语法。这一选项将禁止 GNU C 的某些特色, 例如 asm 或 typeof 关键词。 |
-c | 只编译并生成目标文件。 |
-DMACRO | 以字符串”1”定义 MACRO 宏。 |
-DMACRO=DEFN | 以字符串”DEFN”定义 MACRO 宏。 |
-E | 只运行 C 预编译器。 |
-g | 生成调试信息。GNU 调试器可利用该信息。 |
-IDIRECTORY | 指定额外的头文件搜索路径DIRECTORY。 |
-LDIRECTORY | 指定额外的函数库搜索路径DIRECTORY。 |
-lLIBRARY | 连接时搜索指定的函数库LIBRARY。 |
-m486 | 针对 486 进行代码优化。 |
-o FILE | 生成指定的输出文件。用在生成可执行文件时。 |
-O0 | 不进行优化处理。 |
-O 或 -O1 | 优化生成代码。 |
-O2 | 进一步优化。 |
-O3 | 比 -O2 更进一步优化,包括 inline 函数。 |
-shared | 生成共享目标文件。通常用在建立共享库时。 |
-static | 禁止使用共享连接。 |
-UMACRO | 取消对 MACRO 宏的定义。 |
-w | 不生成任何警告信息。 |
-Wall | 生成所有警告信息。 |
-funroll-loops
gcc来检查代码,进行循环展开,减少循环次数提高性能
https://blog.csdn.net/qq_31108501/article/details/51842166
-O0: 不做任何优化,这是默认的编译选项。
-O和-O1: 对程序做部分编译优化。使用本项优化,编译器会尝试减小生成代码的尺
寸,以及缩短执行时间。
打开的优化选项:
-fdefer-pop 延迟栈的弹出时间。
-fmerge-constants 尝试横跨编译单元合并同样的常量
-fthread-jumps 分支重定向
-floop-optimize 常量表达式从循环中移除,简化判断循环的条件
-fif-conversion 将条件跳转转换为等价的无分支型式
-fif-conversion2
-fdelayed-branch 根据指令周期时间重新安排指令
-fguess-branch-probability 采用随机模式猜测分支被执行的可能性
-fcprop-registers 减少调度依赖性并且删除不必要的寄存器复制操作
-O2: 是比O1更高级的选项,进行更多的优化。提高了生成代码的执行效率。
-fforce-mem
-foptimize-sibling-calls
-fstrength-reduce
-fcse-follow-jumps
-fcse-skip-blocks
-frerun-cse-after-loop
-frerun-loop-opt
-fgcse-lm
-fgcse-sm
-fgcse-las
-fdelete-null-pointer-checks
-fexpensive-optimizations
-fregmove
-fschedule-insns
-fschedule-insns2
-fsched-interblock
-fsched-spec-load
-fcaller-saves
-fpeephole2
-freorder-blocks
-freorder-functions
-fstrict-aliasing
-funit-at-a-time
-falign-functions
-falign-jumps
-falign-loops
-falign-labels
-fcrossjumping
-O3:比O2更进一步的进行优化。
-finline-functions
-fweb
-frename-registers
-funswitch-loops
-Os: 主要是对程序的尺寸进行优化。
Os会关闭如下选项: -falign-functions -falign-jumps -falign-loops -falign-labels -freorder-blocks -fprefetch-loop-arrays
O1优化会消耗少多的编译时间,它主要对代码的分支,常量以及表达式等进行优化。
O2会尝试更多的寄存器级的优化以及指令级的优化,它会在编译期间占用更多的内存和编译时间。
O3在O2的基础上进行更多的优化,例如使用伪寄存器网络,普通函数的内联,以及针对循环的更多优化。
Os主要是对代码大小的优化,我们基本不用做更多的关心。 通常各种优化都会打乱程序的结构,让调试工作变得无从着手。并且会打乱执行顺序,依赖内存操作顺序的程序需要做相关处理才能确保程序的正确性。
http://www.runoob.com/w3cnote/gcc-parameter-detail.html
https://wizardforcel.gitbooks.io/100-gcc-tips/content/address-sanitizer.html
1.令牌桶算法
令牌生成算法:
A.投资收益比值法
B.二分逼近法
C.等比法
https://cloud.tencent.com/developer/article/1692032
C++的初始化有很多方式:默认初始化,值初始化,直接初始化,拷贝初始化,列表初始化
默认初始化是指定义变量时没有指定初值时进行的初始化操作。
(1)内置类型变量(如int,double,bool等),如果定义在语句块外(即{}外),则变量被默认初始化为0;如果定义在语句块内(即{}内),变量将拥有不确定的值。
#include <iostream>
using namespace std;
int a;
int main()
{
int b;
cout << a << endl;
cout << b << endl;
return 0;
}
输出结果:
(2)对于类类型的变量(如string或其他自定义类型),不管定义于何处,都会执行默认构造函数。如果该类没有默认构造函数,则会引发错误。因此,建议为每个类都定义一个默认构造函数(=default)。
示例一:默认构造函数由编译器自动生成
#include <iostream>
using namespace std;
class testA
{
public:
void printf() const
{
cout << data << endl;
}
private:
int data;
};
testA a;
int main()
{
testA b;
a.printf();
b.printf();
return 0;
}
输出结果:
示例二:定义一个构造函数,阻止编译器自动生成默认构造函数
#include <iostream>
using namespace std;
class testA
{
public:
testA(int a)
{
data = a;
}
void printf() const
{
cout << data << endl;
}
private:
int data;
};
testA a;
int main()
{
testA b;
a.printf();
b.printf();
return 0;
}
编译出错:error:no matching function for call to ‘testA::testA()’
a与b都无法初始化
值初始化是值使用了初始化器(即使用了圆括号或花括号)但却没有提供初始值的情况。
#include <string>
#include <vector>
#include <iostream>
struct T1
{
int mem1;
std::string mem2;
}; // 隐式默认构造函数
struct T2
{
int mem1;
std::string mem2;
T2(const T2&) { } // 用户提供的复制构造函数
}; // 无默认构造函数
struct T3
{
int mem1;
std::string mem2;
T3() { } // 用户提供的默认构造函数
};
std::string s{}; // 类 => 默认初始化,值为 ""
int main()
{
int n{}; // 标量 => 零初始化,值为 0
double f = double(); // 标量 => 零初始化,值为 0.0
int* a = new int[10](); // 数组 => 每个元素的值初始化
// 每个元素的值为 0
T1 t1{}; // 有隐式默认构造函数的类 =>
// t1.mem1 被零初始化,值为 0
// t1.mem2 被默认初始化,值为 ""
// T2 t2{}; // 错误:类无默认构造函数
T3 t3{}; // 有用户提供默认构造函数的类 =>
// t3.mem1 被默认初始化为不确定值
// t3.mem2 被默认初始化,值为 ""
std::vector<int> v(3); // 值初始化每个元素
// 每个元素的值为 0
std::cout << s.size() << ' ' << n << ' ' << f << ' ' << a[9] << ' ' << v[2] << '\n';
std::cout << t1.mem1 << ' ' << t3.mem1 << '\n';
delete[] a;
}
输出结果:
直接初始化是指采用小括号的方式进行变量初始化(小括号里一定要有初始值,如果没提供初始值,那就是值初始化了!)
#include <string>
#include <iostream>
#include <memory>
struct Foo {
int mem;
explicit Foo(int n) : mem(n) {}
};
int main()
{
std::string s1("test"); // 自 const char* 的构造函数
std::string s2(10, 'a');
std::unique_ptr<int> p(new int(1)); // OK:允许 explicit 构造函数
// std::unique_ptr<int> p = new int(1); // 错误:构造函数为 explicit
Foo f(2); // f 被直接初始化:
// 构造函数形参 n 从右值 2 复制初始化
// f.mem 从形参 n 直接初始化
// Foo f2 = 2; // 错误:构造函数为 explicit
std::cout << s1 << ' ' << s2 << ' ' << *p << ' ' << f.mem << '\n';
}
输出结果:
拷贝初始化是指采用等号(=)进行初始化的方式。
#include <string>
#include <utility>
#include <memory>
struct A
{
operator int() { return 12; }
};
struct B
{
B(int) {}
};
int main()
{
std::string s = "test"; // OK:构造函数非 explicit
std::string s2 = std::move(s); // 此复制初始化进行移动
// std::unique_ptr<int> p = new int(1); // 错误:构造函数为 explicit
std::unique_ptr<int> p(new int(1)); // OK:直接初始化
int n = 3.14; // 浮点整型转换
const int b = n; // 不受 const 与否
int c = b; // ……影响
A a;
B b0 = 12;
// B b1 = a; // < 错误:要求从 'A' 到非标量类型 'B' 的转换
B b2{a}; // < 等同:调用 A::operator int() ,然后是::B(int)
B b3 = {a}; // <
auto b4 = B{a}; // <
// b0 = a; // < 错误:需要重载赋值运算符
}
(1)对于内置类型变量(如int,double,bool等),直接初始化与拷贝初始化差别可以忽略不计。
(2)对于类类型的变量(如string或其他自定义类型),直接初始化调用类的构造函数(调用参数类型最佳匹配的那个),拷贝初始化调用类的拷贝构造函数。
特别的,当对类类型变量进行初始化时,如果类的构造函数采用了explicit修饰而且需要隐式类型转换时,则只能通过直接初始化而不能通过拷贝初始化进行操作。
在C++ 11之前,所有对象的初始化方式是不同的。
直接初始化(小括号)
拷贝初始化(等号)
值初始化(花括号) C++ 11努力创造一个统一的初始化方式。 其语法是使用{}和std::initializer_list。(列表初始化)
它采用一对花括号(即{})进行初始化操作。能用直接初始化和拷贝初始化的地方都能用列表初始化,而且列表初始化能对容器进行方便的初始化。
int values[]{1,2,3};
vector<int> v{2,3,5,7,11,12,17};
vector<string> cities{"Berlin","New York","London"};
complex<double> c{4.0,3.0};
原理:
编译器看到{t1,t2,…tn}的参数列表,首先自动调用参数初始化,将其转换成initializer_list
int i; //i没有定义值
int j{}; //初始化为0
int *p; // 没有定义值
int *q{}; //初始化为nullptr
构造函数
#include <string>
#include <iostream>
#include <memory>
using namespace std;
class P {
public:
P(int a,int b) { cout << "P(int, int), a=" << a << ", b=" << b << endl; }
};
int main()
{
P p(77,5);
P q{77,5};
P r{77,5,42};
P s = {77, 5};
return 0;
}
C++会先使用{}中的参数生成一个std::initializer_list对象,然后调用赋值对象的构造函数。
在stl库中出现大量的这种使用方式。
forward_list.h
stl_map.h
stl_algo.h
stl_bvector.h
stl_set.h
stl_list.h
basic_string.h
#include <string>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
vector<int> v1{2, 5, 7, 13, 69, 83, 50};
vector<int> v2({2, 5, 7, 13, 69, 83, 50});
vector<int> v3;
v3 = {2, 5, 7, 13, 69, 83, 50};
v3.insert(v3.begin() + 2, {0, 1, 2, 3, 4});
for (auto i : v3)
cout << i << ' ';
cout << endl;
cout << max({string("Ace"), string("Stacy"), string("Sabrina"),
string("Barkley")})<<endl; // Stacy 字典排序
cout << min({string("Ace"), string("Stacy"), string("Sabrina"),
string("Barkley")})<<endl; // Ace
cout << max({54, 16, 48, 5})<<endl; // 54
cout << min({54, 16, 48, 5})<<endl; // 5
return 0;
}
可能出现的问题(Narrowing Initializations)
int x1(5.3); // x1的值为5
int x2 = 5.3; // x2的值为5
int x3{5.0}; // error
int x4{5.3}; // error
char c1{7}; // OK,c1的值是ascii码对应的字符
char c2{99999}; // error ,99999不能与一个字符对应
std::vector<int> v1{1, 2, 4, 5}; // OK
std::vector<int> v2{1, 2.3, 4, 5.6}; // Error
int a {}