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 {}
在C++中声明自定义的类,编译器会默认帮助程序员生成一些未自定义的成员函数。这样的函数被称为“默认函数”。
Big-Five : 默认构造函数,析构函数, 拷贝构造函数, 拷贝赋值运算符、移动构造函数和移动赋值运算符。
拷贝操作:拷贝构造函数、拷贝赋值运算符。
移动操作:移动构造函数、移动赋值运算符
一旦程序员实现了这些函数的自定义版本,则编译器不会再为这类自动生成默认版本。(又称特种成员函数)
类默认函数的控制:“=default” 和“=delete” 函数
“=default”, 显式地指示编译器生成该函数的默认版本。
“=delete”,会指示编译器不生成函数的缺省版本。一旦缺省版本被删除了,重载该函数也是非法的。
默认构造函数是可以无实参调用的构造函数(以空参数列表定义,或为每个形参提供默认实参而定义)。拥有公开默认构造函数的类型,是可默认构造的。
仅当类中不包含用户声明的构造函数时,才生成。
示例一:没有定义默认构造函数,创建对象时,隐式生成一个默认构造函数
class X {
private:
int a;
};
X x; //可以编译通过
自动生成的默认构造函数没有参数,包含一个空的函数体,即 X::X(){ }。
示例二:没有定义默认构造函数,自定义了非默认构造函数,出现编译错误
class X {
public:
X(int i) { a = i; } //用户自定义的构造函数
private:
int a;
};
X x; // 错误 , 默认构造函数 X::X() 不存在
不再隐式的生成默认构造函数,如果需要用默认构造函数来创建类的对象,必须显式定义。
示例三:手动定义默认构造函数
class X {
public:
X(){}; // 手动定义默认构造函数
X(int i) { a = i; }
private:
int a;
};
X x; // 正确,默认构造函数 X::X() 存在
问题:
示例四:defaulted函数,编译器自动生成默认构造函数
class X {
public:
X() = default;
X(int i) { a = i; }
private:
int a;
};
X x;
在C++11新标准中,如果我们需要默认的行为,那么可以通过在参数列表后面写上=default来要求编译器生构造函数
示例五:使类型恢复为POD类型
POD (Plain Old Data) 用于说明一个类型的属性,尤其是用户自定义类型的属性。
Plain 表示POD是个普通的类型,C++中常见的类型都有这样的属性。
Old 体现与C的兼容性。
#include <iostream>
#include <type_traits>
using namespace std;
class TwoCstor {
public:
//提供了带参数版本的构造函数,则必须自行提供不带参数版本,且TwoCstor不再是POD类型
TwoCstor() {}
TwoCstor(int i) : data(i) {}
private:
int data;
};
int main() {
cout << is_pod<TwoCstor>::value << endl; //0
}
#include <iostream>
#include <type_traits>
using namespace std;
class TwoCstor {
public:
//提供了带参数版本的构造函数,再指示编译器提供默认版本,则自定义类型依然是POD类型
TwoCstor() = default;
TwoCstor(int i) : data(i) {}
private:
int data;
};
int main() {
cout << is_pod<TwoCstor>::value << endl; //1
}
C++11,在函数的定义或者声明加上”=delete”,避免隐式数据类型转换。
示例六:避免隐式数据类型转换
class ConvType {
public:
ConvType(int i){};
ConvType(char c) = delete; //删除char 版本
};
void Func(ConvType ct) {}
int main() {
Func(3);
Func('a'); //无法通过编译
ConvType ci(3);
ConvType cc('a'); //无法通过编译
}
// 编译结果 error: use of deleted function ‘ConvType::ConvType(char)’
如果没有ConvType(char c) = delete; 编译器会隐式将’a’转化成int型,并调用int型的构造函数。
explicit关键字防止隐式转换。
示例七: 不建议将“=delete”与explicit合用
class ConvType {
public:
ConvType(int i){};
explicit ConvType(char c) = delete;
};
void Func(ConvType ct) {}
int main() {
Func(3);
Func('a'); //可以通过编译
ConvType ci(3);
ConvType cc('a'); //无法通过编译
}
析构函数时对象生存期终结时调用的特殊成员函数。析构函数的目的是释放对象可能在其生存期间获得的资源。
当一个对象超出作用域或执行delete的时候,析构函数就会被调用。
仅当基类的析构函数为虚的,派生类的析构函数才是虚的。
C++11,默认地,所有的析构函数(无论是用户定义的,还是编译器自动生成的)都隐式的具备noexcept性质。
(noexcept表示其修饰的函数不会抛出异常。如果noexcept修饰的函数抛出了异常,编译器可以选择直接调用std::terminate()函数来终止程序的运行)
示例八:自动隐式生成析构函数
class X {
private:
int x;
};
class Y : public X {
private:
int y;
};
int main() {
X *x = new Y;
delete x;
}
在销毁类X的对象的时候,需要调用类X的析构函数。没有为类X定义析构函数,编译器会自动隐式的为该类生成一个析构函数,自动生成的析构函数没有参数,包含一个空的函数体,即X::~X(){ }。
问题:内存泄漏
执行delete x的时候,系统调用类X的析构函数,编译器无法析构派生类的int型变量 y。
C++98:
将基类的析构函数定义为虚函数,利用delete语句删除指向派生类对象的基类指针时,系统会调用相应的派生类析构函数(实现多态性),从而避免内存泄漏。但是编译器隐式自动生成的析构函数都是非虚函数,因此需要手动为基类定义虚构函数。
示例九:手动定义基类虚析构函数
class X {
public:
virtual ~X(){}; // 手动定义虚析构函数
private:
int x;
};
class Y : public X {
private:
int y;
};
int main() {
X *x = new Y;
delete x;
}
问题:
自动生成->手动编写, 工作量加大
手动编写的析构函数的代码执行效率比编译器自动生成的析构函数低。
阻止了移动操作的生成。(大三律)
示例十:defaulted函数,编译器自动生成函数体。
class X {
public:
virtual ~X() = default; // 编译器自动生成 defaulted 函数定义体
private:
int x;
};
class Y : public X {
private:
int y;
};
int main() {
X *x = new Y;
delete x;
}
编译器会自动生成虚析构函数 virtual X::X(){},该函数比用户自己定义的虚析构函数具有更高的代码执行效率。
示例十一:需要对象在指定内存位置进行内存分配,并且不需要析构函数来完成一些对象级别的清理。
class X {
public:
~X() = delete;
private:
int x;
};
类T的复制构造函数时非模板构造函数,其首个形参为T&、const T& 、volatile T& 或const volatile T&, 而且要么没有其他形参,要么剩余形参均有默认值。
按成员进行非静态数据成员的拷贝构造。仅当类中不包含用户声明的拷贝构造函数时才生成。如果该类声明了移动操作,则拷贝构造函数将被删除。
拷贝构造函数被调用的场景:
一个对象需要通过另一个对象进行初始化
一个对象以值传递的方式作为参数传入函数
一个对象以值传递的方式作为返回值从函数返回
示例:自动生成默认拷贝构造函数
class X {
public:
X();
};
int main() {
X x1;
X x2 = x1; // 正确,调用编译器隐式生成的默认拷贝构造函数
}
没有显式声明定义对应类的拷贝构造函数,C++编译器会自动生成拷贝构造函数。
C++编译器提供的拷贝构造函数工作方式是浅拷贝
浅拷贝只是单纯拷贝了该成员的内存地址,但所指的空间内容没有被复制,而是由两个对象共用,容易出现double free的问题。
解决办法:1. 手动重载拷贝构造函数,实现深拷贝
2. 禁止编译器生成默认的拷贝构造函数, 拒绝不安全的浅拷贝
需求:禁止编译器生成默认的拷贝构造函数, 拒绝不安全的浅拷贝。
C++98,将拷贝构造函数声明为private的成员,并且不提供函数实现。
一旦有人试图(或者无意识)使用拷贝构造函数,编译器就会报错
示例十二:C++98 禁止使用拷贝构造函数
#include <iostream>
#include <type_traits>
using namespace std;
class NoCopyCstor {
public:
NoCopyCstor() = default;
private:
NoCopyCstor(const NoCopyCstor &);
//将拷贝构造函数声明为private成员并不提供实现,可以有效阻止用户错用拷贝构造函数
};
int main() {
NoCopyCstor a;
NoCopyCstor b(a); //无法通过编译
}
//编译结果:
//error: ‘NoCopyCstor::NoCopyCstor(const NoCopyCstor&)’ is private
C++11,在函数的定义或者声明加上”=delete”,会指示编译器不生成函数的缺省版本。
示例十三:C++11,禁止使用拷贝构造函数
#include <iostream>
#include <type_traits>
using namespace std;
class NoCopyCstor {
public:
NoCopyCstor() = default;
NoCopyCstor(const NoCopyCstor &) = delete;
//使用“=delete”同样可以有效阻止用户错用拷贝构造函数
};
int main() {
NoCopyCstor a;
NoCopyCstor b(a); //无法通过编译
}
//编译结果
// error: use of deleted function ‘NoCopyCstor::NoCopyCstor(const NoCopyCstor&)’
类T的复制赋值运算符是名为operator=的非模板非静态成员函数,它接收恰好一个T、T&、const T&、volatile T&或const volatile T &类型的形参。对于可复制赋值类型,它必须有公开的拷贝赋值运算符。
按成员进行非静态数据成员的拷贝赋值。仅当类中不包含用户声明的拷贝赋值运算符时才生成。
如果该类声明了移动操作,则复制构造函数将被删除。在已经存在复制构造函数或析构函数的条件下,仍然生成复制赋值运算符已经成为了被废弃的行为。
默认拷贝赋值操作符:浅拷贝
需要:1. 重载实现深度赋值
2.禁用拷贝赋值操作符
示例十四:自动生成默认拷贝赋值操作符
class X {
public:
X();
};
int main() {
X x1;
X x2 = x1; // 正确,调用编译器隐式生成的默认拷贝构造函数
X x3;
x3 = x1; // 正确,调用编译器隐式生成的默认拷贝赋值操作符
}
没有显式声明定义对应类的拷贝构造函数,C++编译器会自动生成拷贝赋值操作符,X &operator=(const X &){}。
示例十五:禁止类对象之间的赋值。
class X {
public:
X();
X(const X &) = delete; // 声明拷贝构造函数为 deleted 函数
X &operator=(const X &) = delete; // 声明拷贝赋值操作符为 deleted 函数
};
int main() {
X x1;
X x2 = x1; // 错误,拷贝构造函数被禁用
X x3;
x3 = x1; // 错误,拷贝赋值操作符被禁用
}
拷贝构造函数 VS. 拷贝赋值运算符
拷贝构造函数在创建或初始化对象的时候调用;
拷贝赋值运算符在更新一个对象的值时调用;
两种拷贝操作是彼此独立的。如果声明了一个拷贝构造函数,未声明拷贝赋值运算符,在进行拷贝赋值的时候,编译器会自动生成拷贝赋值运算符。同理,如果声明了一个拷贝赋值运算符,未声明拷贝构造函数,进行拷贝构造的时候,编译器会自动生成拷贝构造函数。
大三律(Rule of Three)
如果你声明了拷贝构造函数,拷贝赋值运算符,或析构函数中的任何一个,你就得同时声明所有的三个。
标准库中用以管理内存的类(如执行动态内存管理的STL容器)都会遵从大三律。
类T的移动构造函数时非模板构造函数,其首个形参是T &&、const T&&、volatile T&& 或 const volatile T&&, 且无其他形参,或剩余形参均有默认值
如果现有的对象是左值(lvalue),那么是拷贝构造函数,如果现有的对象是右值(rvalue)(例如,即将被摧毁的临时对象),那么是移动构造函数。
IntCell B = C; // 如果C是左值,那么是拷贝构造函数;如果C是右值,那么是移动构造函数
IntCell B { C }; // 如果C是左值,那么是拷贝构造函数;如果C是右值,那么是移动构造函数
class Widget {
public:
Widget(Widget &&rhs); //移动构造函数
Widget &operator=(Widget &&rhs); //移动赋值运算符
};
两种移动操作并不彼此独立,声明了其中一个,就会阻止编译器生成另外一个。
一旦显式声明了拷贝操作,就不会再生成移动操作了。
一旦声明了移动操作,编译器就会废除拷贝操作。(“=delete”)
受到大三律的影响:用户只要声明了析构函数,就不会生成移动操作。
移动操作生成条件(三者同时成立):
示例一:defaulted函数既可以在类体内(inline)定义,也可以在类体外(out-of-line)定义。
class X {
public:
X() = default; // Inline defaulted 默认构造函数
X(const X &);
X &operator=(const X &);
~X() = default; // Inline defaulted 析构函数
};
X::X(const X &) = default; // Out-of-line defaulted 拷贝构造函数
X &X::operator=(const X &) = default; // Out-of-line defaulted, 拷贝赋值操作符
示例二:defaulted函数的错误用法
class X {
public:
int f() = default; // 错误 , 函数 f() 非类 X 的特殊成员函数
X(int) = default; // 错误 , 构造函数 X(int, int) 非 X 的特殊成员函数
X(int = 1) = default; // 错误 , 默认构造函数 X(int=1) 含有默认参数
};
示例三:deleted函数,禁用某些用户自定义的类new操作符,避免在自由存储区创建类的对象。
#include <cstddef>
using namespace std;
class X {
public:
void *operator new(size_t) = delete;
void *operator new[](size_t) = delete;
};
int main() {
X *pa = new X; // 错误,new 操作符被禁用
X *pb = new X[10]; // 错误,new[] 操作符被禁用
}
示例四:deleted函数,用于普通函数禁止类型转换
#include <iostream>
using namespace std;
int add(int, int) = delete;
double add(double a, double b) { return a + b; }
int main() {
cout << add(1, 3) << endl; // 错误,调用了 deleted 函数 add(int, int)
cout << add(1.2, 1.3) << endl;
return 0;
}
delete禁用的仅是函数的定义,即该函数不能被调用,但是函数标识符add仍然有效,在名字查找和函数重载解析时仍会找到该函数标识符。
示例五:成员函数模板在任何情况下都不会抑制特种成员函数的生成。
class Widget {
public:
template <typename T>
Widget(const T &rhs); //以任意型别构造Widget
template <typename T>
Widget &operator=(const T &rhs); //以任意型别对Widget赋值
};
“=default” 修饰的函数为显式缺省(explicit defaulted)函数。仅适用于类的特殊成员函数,且该特殊成员函数没有默认参数。
“=delete” 修饰的函数为删除函数。
功能:
“=default” 使类型恢复为POD类型。(示例五)
2. “=default”,不仅可以用于类的定义中修饰成员函数,也可以在类定义之外修饰成员函数。在类定义外显式指定缺省版本所带来的好处是,可以对一个class定义提供多个实现版本。(补充示例一)
3. C++11标准并不要求编译器为某些函数提供缺省的实现,但如果将其声明为显式缺省的话,则编译器会按照某些“标准行为”为其生成所需要的版本。比如:“operator ==”。
4. "=delete"可以避免用户使用一些不应该使用的类的成员函数(示例十三)。
参考链接:
[1]https://www.cnblogs.com/lsgxeva/p/7787438.html
Benchmark: 程序名称
Time: 迭代一次的平均用户时间
CPU:迭代一次的平均CPU时间
Iterations: 总迭代次数
| 允许三种格式的输出,console、json、csv, 默认为console, 使用–benchmark_format=<console | json | csv>指定。 |
使用–benchmark_out=<filename>可将结果输出到指定的文件中。
传递和获取参数:
1.传递参数使用BENCHMARK宏生成的对象的Arg方法
2.传递进来的参数会被放入state 对象内部存储,通过range方法获取,调用时的参数0对应第一个参数
3.Arg 方法一次只能传递一个参数,使用Args方法传递多个参数
在benchmark里面,如果每个迭代会有一些额外的setup, 可能会需要在循环里面做。但是一般来说在benchmark时间统计里面把这部分去掉。有两个函数可以做这个事情:PauseTiming()和ResumeTiming()事实上在循环里面使用这两个函数,输出结果里面时间额外多了很多。
Time是一次操作的用户时间
CPU是一次操作的CPU时间
https://www.cnblogs.com/apocelipes/p/10348925.html
c++性能测试工具:google benchmark入门(一)