C++ Basic Notes
Basic Workflow
初始化与回收
- Treat global program as a object: 开工函数与收工函数
- Normal Object: 构造函数与析构函数
变量
const 变量 代替 #define 宏定义
指针
- 只读指针可赋值为 普通对象地址
- 普通指针不可赋值为 只读对象地址
int *p = const int; // x
int *p = int; // o
const int *p = const int; // o
const int *p = int; // o
reference (引用类型)
性质
引用类型(&)实质上是一种语法糖,编译器将引用变量最终全部编译为指针变量
作为函数参数
向函数传递引用类型实参时,若:
- 实参与形参类型不一致(必须完全一致)
- 实参为(右)值表达式
则会生成一个内部匿名变量,用于函数调用. 此时,对参数进行的操作将无法改变实参原有值,使得 call by reference 失效.
特性
- 普通全局引用变量必须在定义时初始化初始化(左值表达式)
int &x = 1; // warning: 引用匿名变量
int &k = j++; // warning: 引用匿名变量
- 引用变量不分配存储单元: 不能引用 引用变量, 不能作为指针所指对象, 不能作为数组元素
int & &x; // error
int & *p; // error
int & s[4]; // error
- 引用变量的值按字节(指针/地址)编码: 不能引用位段(无法按字节编码)
int &w = a.j; // error
- 引用变量不能引用引用变量, 只能引用另一个引用变量所引用的变量/地址
int x = 10;
int &j = x;
int &&m = j; // error
int &m = j; // pass: m = j => x
- 引用变量可以引用 register 变量, 编译器会将其自动转为 auto 变量(为其分配地址)
register int i = 0,
register int &j = i;
- 引用变量在一定程度上具有指针性质
int x = 1;
const int &p = x; // pass
x = 7; // pass
p = 7; // error
volatile 类型
- volatile 表示可被其他线程/进程改变的变量
- volatile 变量常作为全局变量, 用于同步各进程
- const 表示不可被本线程/进程修改的变量
volatile int x;
x = 3;
if (4 == x) {
cout << "X changed by other routines.";
}
运算符
左/右值运算符(运算结果为左/右值)
- 左值运算符: 前置
++
/--
,=
/+=
/*=
/&=
/op=
. - 右值运算符: 强制类型转换(不可对其运算结果进行取地址),
+
/*
/binary
.
sizeof
sizeof 数值
sizeof(数值)
sizeof(类型)
sizeof(printf("abcd")); // 无输出
Expression (表达式)
左/右值表达式
左值表达式一定可作(右)值表达式, (右)值表达式不可作左值表达式:
- 左值表达式 : 变量, 赋值表达式, 前缀自操作表达式, 返回值为引用类型的函数调用
bar;
y = 6;
i -= 10;
++i;
int &f(void);
f() = j;
- (右)值表达式: 常量, 强制类型转换表达式, 后缀自操作表达式, 算数表达式
20;
(type)x;
i++;
y + 2;
联合(union)
匿名联合具有以下性质:
- 没有对象的全局匿名联合必须 static
- 只可定义 public 成员
- 数据成员与联合本身作用域相同
- 数据成员共享存储空间
位段
class/struct/union 都可定义位段成员, 但类型必须为 char/short/int/enum, 不可为 long/float/array/class
函数
Default Params
- 不能在 函数原型声明 与 函数定义 中 同时 定义参数的默认值,会产 生默认值冲突
- 所有缺省参数必须位于参数列表右边
- 不能用前一个参数初始化后一个参数
- 当同时有
int g(void)
int g(int x = 1)
时, 不能调用 g() (具有二义性)
int bar(int x , int y = 5, int z = m(u,v));
int foo(int x, int y = x++); // error
内联函数(inline)
以下情况会造成内联失败:
- 内联函数中使用 分支/循环/开关/函数调用
- 内联函数定义出现在调用后面(先调用后定义)
- 其他函数访问了内联函数入口地址
- 内联函数定义为(纯)虚函数
内联最终结果:
- 内联成功后, 原函数会被编译器清除
- 不管内联是否成功, 内联函数作用域局限于当前源文件
- 全局 extern main 函数不能定义为内联函数(否则会使得主函数作用域变小, 操作系统无法访问主函数)
- 在类体内实现的任何函数自动变为内联函数
类
访问控制权限
public > protected > private + friend > private
构造函数
调用形式
构造函数不可被显式调用(类前缀), 必须隐式调用(省略类前缀)
构造函数体
- 构造函数体前: 初始化只读成员、引用成员、对象成员、其他数据成员, 初始化顺序以定义顺序为准, 无关构造函数体前出现顺序
- 构造函数体内: 再次赋值其他数据成员(不可再次只读成员、引用成员、对象成员)
默认无参构造函数
- 当定义了含参构造函数后, 编译器将不会再为对象自动添加无参构造函数
- 默认无参构造函数不会初始化只读/引用成员, 且只会调用对象成员的无参构造函数(若对象成员没有无参构造函数,则编译器报错)
- 对于局部对象, 将随机初始化普通数据成员; 对于全局对象, 将普通数据成员初始化为 0. 由于默认无参构造函数的存在, 当对象只含有普通数据成员(无只读/引用/指针成员, 且对象成员有无参构造函数), 可以不显式定义构造函数
class Foo {
const int b;
int c, &d, e, f;
String g, h;
public:
// 初始化顺序: b, c, d, e, f, g, h
Foo(int bar): d(c), c(bar), g(bar), b(bar), e(bar) {
c += bar;
f = bar;
}
};
B z(7, 8); => B z(7, 8); ///< 2 参
B z = (7, 8); => B z(8); ///< 1 参
构造顺序
- 同一派生树上所有虚基类(自左向右, 自下向上)(递归)
- (继承顺序)直接基类(递归)
- (定义顺序)所有成员
- 构造函数体
深拷贝构造函数
- 形式为 Foo:Foo(Foo &obj) 的构造函数,可使得对象作为实参传递时自动进行深拷贝复制
ARRAY::ARRAY(ARRAY &r) {
p = new int[size = r.size];
for (int i = 0;i < size; i++) {
p[i] = r.p[i];
}
}
Move Constructor
class A {
A(const A&& a) {
// move constructor
// set a == null_ptr
}
}
析构函数
- 析构函数即可显式调用,又可隐式调用
- 析构函数与全局 main 函数 没有重载函数
- 作用域结束时会自动调用析构函数
- 调用 exit/abort 时, 需手动调用析构函数释放资源
String x("global");
int main(void) {
short error = 0;
String y("local");
// set error flag
switch (error) {
case 0:
return;
case 1:
y.~String();
exit(1);
default:
x.~String();
y.~String();
abort();
}
return 0;
}
- 设置 析构标志 防止重复析构同一对象
String::~String() {
// check flag
if (s == NULL) {
return;
}
cout<<"Deconstruct:"<<s;
free(s);
// set flag
s = NULL;
}
构造与析构(重点)(P148)
- 按定义顺序自左下至右上地构造所有虚基类
- 按定义顺序构造直接基 类
- 按定义顺序构造数据成员(对象/const/引用/普通成员)
- 执行类构造函数体
- 递归执行以上过程
派生树
- 一个对象/对象成员一颗单独的派生树
- 单独的派生树中, 合并同名虚基类, 不合并同名基类,
并
{ name(type), ...}
标示数据成员
New and Delete
Stack and Heap
- string str("sabertazimi") 创建在栈上, 自动析构
- new/malloc 返回堆指针, delete/free 的对象是堆指针/(&引用变量), 完全由程序员管理创建与回收
int x = 5;
int *p = &x;
int &q = x;
delete p; // address of x
delete &q; // address of x
指针成员
- 普通指针/不含指针成员的对象变量分配/回收内存可混用 malloc/new/free/delete/delete []
- 创建/回收含有指针成员的类时,只能用
new/delete/delete [](分配对象内存+调用构造/析构函数)
, 不能用 malloc/free(只作用于对象本身,不调用构造/析构函数,即不为指针成员分配/回收内存), 否则会造成指针成员未分配内存/内存泄露 - new 对象数组实质: malloc 对象 + 调用对象无参构造函数
This Pointer
普通函数成员比静态函数成员多一个隐含参数 this 指针, 其会随着函数类型的不同而改变类型
- this 指针指向对象起始地址处(对象首成员地址)
- 一般函数:
class_type *const this;
- const 函数:
const class_type *const this;
, 可以修改 this 所指对象的非只读静态数据成员 - volatile 函数:
volatile class_type *const this;