来自youtube cherno C++视频的一些知识点。
- 对函数的调用在汇编中对应一句call func语句,其中func是一个函数的签名(signature)
- 对程序而言,即使只有一个文件,链接器也需要链接,因为它需要链接程序入口点(entry point)
- 一个程序的入口点不必一定是名为main的函数
- 如果声明了一个函数A但是没有调用该函数的语句,那么不会报链接错误,因为根本不需要链接该函数;如果有该调用语句且所在的函数B是static,且所在static函数没有被调用,则也不会报链接错误,因为该static函数B不会被其他文件使用,且在本文件中static函数B也没有被调用,所以不需要链接B,从而也就不需要链接函数A
- 类型之间的最大区别就是所占的内存不同,最明显的例子便是char与int之间的转换,因为对于内存而言,没有什么“类型”而言,而只用该类型占用了多少“空间”的区别
- bool 占用一个字节,0 == false,true通常为1,但不一定,任何非零值都视为true
- debug模式中编译器对未初始化的变量赋值0xCC,这是release模式下不会做的事情,所以看到0xCC时,你应该知道这个变量没有被初始化
- if语句的汇编对应着test/jne/jmp/cmp等指令
- C++中并没有
else if
关键字,只有else
和if
关键字,所谓的else if实际上是else部分省略了大括号,直接接上if语句
if (condition1)
func1();
else if(condition2)
func2();
//等价于
if (condition1)
func1();
else {
if(condition2)
func2();
}
- 指针是一个整数,它是一个内存地址。指针类型用于解引用时应该读或写的字节数
- reference只是原变量的别名(alias),
- static在非class和struct中表示symbol(函数或变量名)仅在本translation unit(一个cpp文件)中对linker可见,在class和struct中声明static变量表示所有class/struct instance都共享这个变量(只有一份),这个变量可以理解为只是声明在一个namespace中,其生命周期是从程序开始到程序结束,static方法则失去了非static方法的隐含的第一个参数–对象指针this,与普通函数一样
- enum可以指定整数类型
enum Test : unsigned char {
A, B, C
}
enum并不构成一个命名空间,所以你可以直接指定enum值,而不需要enum名称,例如:
class TestClass {
public:
enum Test : unsigned char {
A, B, C
}
}
可以直接使用TestClass::A
14.
int a[3];
a[2] = 1;
// 等价于
*(int*)((char*)a + 2*sizeof(int)) = 1;
// 即地址加8并取4字节大小的内存写入数据1
- 对分配在栈上的数组,和分配在堆上的数组,sizeof的含义不同
int a1[3];
int size1 = sizeof(a1); // 数组总大小:3 * 4 = 12
int* a2 = new int[3];
int size2 = sizeof(a2); // int*的指针大小:4
char* s = "Hello World";
s[0] = "A"; // undefined behavior
声明一个不带const修饰符的char变量并指向一个字符串常量,虽然不带const,但不应改变其字符串内容,因为这是一个undefined behavior,这个字符串常量存储的位置是可执行文件的代码段,该段仅可读而不可写。如果要声明可以改变其指向内容的char变量,那么char*指向的内存区域应该是可读写的区域,即堆或者栈:
char s[]="Hello World";
s[0] = "A"; // OK
此时s指向的地址在栈上,因此可以改变其指向的内容,注意:字符串常量永远存储在仅可读的区域,我们改变的不是字符串常量本身,而是s指向的内存区域,第一句代码执行后,字符串常量的内容已经拷贝到可读写的栈中。
17.
const int* a = 0; // 指向的内容为const
int const* a = 0; // 与上句等价
int * const a = 0; // 指针本身为const
const是一种承诺,但并非完全不可改变,可以通过强转等形式转为非const,但做此类事情时要非常清楚对此带来的影响。
18. 类中const方法一般不可改变成员变量,但是如果确实有一些变量需要改变,例如debug需要但不影响程序运行结果,那么需要标记此类变量为mutable
class A{
public:
int a;
mutable int b;
int func() {
b = 1; // It's OK
return a;
}
};
- 类成员初始化列表的顺序应该和类成员的定义顺序相同(实际初始化顺序取决于定义顺序而非初始化列表顺序)。成员初始化列表的使用可以避免一次成员类默认构造函数造成的性能损失
class A{
B b; // 第一次构造
A()
{
b = B(1); // 第二次构造
}
};
// =====
class A{
B b;
A() : b = B(1); // 唯一一次构造
{
}
};
new
和delete
也是一种操作符,即operator new
和operator delete
,他们可以被重载。new的底层实现通常会调用malloc函数,传入需要的字节数,返回分配的内存地址,delete的底层实现通常会调用free。对于数组的new和delete,应该使用带中括号的版本
int *a = new int[5]
delete[] a;
对应operator new []
和operator delete[]
21. new支持placement new的方式在指定内存空间分配空余内存,语法如下:
int* addr = new int[64];
memset(addr, 1, 64 * sizeof(int));
int* a = new (addr) int[2]; // <<==
a[0] = 16;
a[1] = 32;
22. C++只允许implicit convertion(隐式转换) 发生一次
23. std::unique_ptr<MyClass> a = std::make_unique<MyClass>();
比std::unique_ptr<MyClass> a(new MyClass())
更安全。std::make_shared类似
24. std::shared_ptr<MyClass> a = std::make_shared<MyClass>();
的性能比std::shared_ptr<MyClass> a(new MyClass());
好,即使用make_shared性能比先new再传递指针性能好,这是因为shared_ptr需要引用计数,他的控制块(control block)需要申请内存,先new的方式申请了两次内存,而make_shared的方式合并了两次申请的内存,只申请了一次
25. shared_ptr赋值给weak_ptr不会增加shared_ptr的引用计数,可以理解为weak_ptr只是一个引用,不会因此增加引用计数,因此使用前需要判断是否有效。
26. 实现拷贝一个对象可以将一个对象的成员逐一拷贝,也可以是如下这样的形式:
class A
{
private:
int a;
int b;
A(const A& other) // copy constructor
{
memcpy(this, &other, sizeof(A)); // 拷贝整个对象的内存
}
};