一、类内初始值和初始化列表的选择:
构造函数的初始化列表 与 类内成员初始化 没有谁好谁不好,谁来替代谁,两种方法可相互补充使用。类内初始化有一些好处:
- 1、当你有多个构造函数时,如果使用初始化列表,每个构造函数都要写一遍,烦人不说,同时产生重复代码,修改易漏。如果把这些成员都用类内初始化,初始化列表就不用再列出它们了。
2、类内初始化,成员之间的顺序是隐式的,会有些便利。如果使用初始化列表,它是有顺序之分的,顺序不对,编译器会警告。
3、对于简单的类或结构,没有构造函数的,可以直接用类内初始化在成员声明的同时直接初始化,方便。
对于一些类类型的成员初始化要小心,如果成员之间有依赖关系,这时使用初始化列表显式的指明这些成员的构造(初始化)顺序是比较稳妥的。
如果成员已经使用了类内初始化,但在构造函数的初始化列表又列出来,编译器以后者优先,类内初始化会被忽略。如果某些成员使用不同构造函数时,会有不同的默认值,这种情况就要用初始化列表。同时,其它成员依然可以使用类内初始化。
二、类的const成员、引用或是未提供默认构造函数的类类型成员,必须用初始值列表初始化而不允许赋值。
class A
{
public:
A(int ii)
{
i = ii;//OK
ri = i;//error,ri未初始化
ci = ii;//error,不允许改变引用的值
}
private:
int i;
int& ri;
const int ci;
};
//正确做法:
A:: A(int ii):i(ii),ri(i),ci(ii) {}
三、不应有两个或两个以上的构造函数起到默认构造函数的作用:
class Sample
{
public:
Sample() = default;
Sample(int ii = 0, char ci = 'a') : i(ii), c(ci) {}
private:
int i;
char c;
};
int main()
{
Sample a; //报错:类Sample包含多个默认构造函数
}
四、使用委托构造函数避免有多个参数表不同但是逻辑相近(或者有公共部分)的构造函数造成的代码重复。
五、通过一个实参调用的构造函数定义了从构造函数的参数类型到类类型的隐式转换规则,但编译器只会执行一步自动转换,可以用explicit来阻止隐式转换。
string s = "abc";// OK,接受const char* 的string构造函数不是explicit的,允许这样的转换
vector<int> vi = 10;// error,接受一个容量参数的vector的构造函数是explicit的,不允许转换
六、static关键字的五种含义:
- 修饰全局变量时,表明一个全局变量只对定义在同一文件中的函数可见。
- 修饰局部变量时,表明该变量的值不会因为函数终止而丢失。
- 修饰函数时,表明该函数只在同一文件中调用。
- 修饰类的数据成员,表明对该类所有对象这个数据成员都只有一个实例。即该实例归所有对象共有。
- 修饰类成员函数,如上。
七、静态成员函数不能是const的:
- 一个静态成员函数只能访问它的参数、类的静态数据成员和全局变量。const修饰符用于表示函数不能修改成员变量的值,该函数必须是含有this指针的类成员函数,函数调用方式为thiscall,而类中的static函数本质上是全局函数,调用规约是__cdecl或__stdcall,不能用const来修饰它。一个静态成员函数访问的值是其参数、静态数据成员和全局变量,而这些数据都不是对象状态的一部分。而对成员函数中使用关键字const是表明:函数不会修改该函数访问的目标对象的数据成员。既然一个静态成员函数根本不访问非静态数据成员,那么就没必要使用const了。
- 定义数据成员为静态变量,以表明此全局数据逻辑上属于该类。定义成员函数为静态函数,以表明此全局函数逻辑上属于该类,而且该函数只对静态数据、全局数据或者参数进行操作,而不对非静态数据成员进行操作。
八、
常量 | 含义 | badbit位 | failbit位 | eofbit位 | 十进制值 |
---|---|---|---|---|---|
ios::badbit | 不可恢复错误 | 1 | 0 | 0 | 4 |
ios::failbit | 可恢复错误 | 0 | 1 | 0 | 2 |
ios::eofbit | 到达文件尾 | 0 | 0 | 1 | 1 |
ios::goofbit | 流有效 | 0 | 0 | 0 | 0 |
九、对已打开的文件流调用open会导致failbit置位,应该先调用close关闭再打开。默认文件打开方式ios::out|ios::trunc,如果要保存原文件内容,应显式指定ios::in|ios::out 或ios::app模式。
十、使用stringstream实现安全的类型转换:
template<class out_type,class in_value>
out_type convert(const in_value & t)
{
stringstream stream;
stream<<t;//向流中传值
out_type result;//这里存储转换结果
stream>>result;//向result中写入值
return result;
}
十一、常用IO操纵符:
作用 | 操纵符 |
---|---|
控制布尔值格式 | boolalpha / noboolalpha |
指定整型值进制 | dec / oct / hex |
在输出中指出进制 | showbase / noshowbase |
指定浮点数打印精度 | (头文件iomanip包含接受参数操纵符)setprecision()(或IO对象的precision成员) |
浮点数计数法 | scientific / fixed / hexfloat / defaultfloat |
打印小数点 | showpoint / noshowpoint |
输出补白 | setw() / left / right / internal / setfill() |
输入是否忽略空白符 | skipws / noskipws |
十二、标准库的istream对象的get(),peek()成员返回值为int而非char,这样做对于某些映射到负值的字符集,也能通过将字符先转换成unsigned char再提升成int而变为正值,从而与EOF区别。
十三、使用emplace_front, emplace, emplace_back操作代替push_front, insert, push_back操作会避免不必要的拷贝及临时对象的产生。emplace利用提供的参数在容器管理的内存直接构造对象。
十四、空容器的begin() end()都是尾后迭代器,所以对空容器的insert():
vector<int> v1;
auto it = v1.insert(v1.begin(), 1); // it仍然是尾后迭代器
插入迭代器原理:
auto it = inserter(cont, iter);//生成插入器
*it = val; //*并无实际意义
//等价于
it =cont.insert(it, val);
++it;//递增指向原来元素
十五、lambda表达式值传递的捕获变量是在创建时拷贝的,而非普通函数的调用时拷贝:
int v1 = 1;
auto f = [v1] {return v1;}; //创建时拷贝,v1=1
v1 = 2; //随后的修改并未影响lambda表达式中的捕获变量
auto j = f(); // j=1
当捕获指针、引用、迭代器时,lambda运行期间,必须确保绑定的对象存在且拥有预期的值。
按值进行传递时,函数体内不能修改传递进来的拷贝,因为默认情况下函数是const的。要修改传递进来的拷贝,可以在参数列表后(此时一定有参数列表,即使为空)添加mutable修饰符。
十六、默认情况下,,动态分配的对象是默认初始化的,类类型调用默认构造函数,内置类型和组合类型对象的值将是未定义的。
十七、new分配const对象是合法的,返回指向const的指针(low-level const),有默认构造函数的类类型对象可以隐式初始化,其他类型对象需要显示初始化:
const string* pstr = new const string;//默认隐式初始化
const int* pi = new const int(1); //需要显式初始化
十八、接受指针参数的智能指针构造函数时explict的,必须使用直接初始化形式:
shared_ptr<int> sp = new int(1); //error,没有从int*到shared_ptr<int>的转换
shared_ptr<int> sp1(new int(1));//OK!
默认情况下,智能指针用delete来释放关联对象,所以用来初始化智能指针的普通指针必须指向动态内存。(自己提供删除器的情况另说)
十九、多个shared_ptr共享的对象,在改变底层对象之前,要先判定我们是否是唯一用户:
if (!p.unique()) //不是唯一用户
p.reset(new int(*p)); //p指向一分新的拷贝,成为唯一用户
//对象操作
二十、unique_ptr不支持拷贝和赋值操作,但支持移动操作:
unique_ptr<int> p1(new int(1));
unique_ptr<int> p2(p1) // error
unique_ptr<int> p3;
p3 = p1; //error
unique_ptr<int> p4(std::move(p1)); //ok
unique_ptr<int> p5 = std::move(p1); //ok
通过release()令原指针释放控制权,其返回的指针可以用来初始化或者reset()另一个unique_ptr
unique_ptr<int> p6(p1.release());
//或者
unique_ptr<int> p7;
p7.reset(p.release());
重载uniqe_ptr删除器需提供删除器类型(shared_ptr则不用):
unique_ptr<objT,delT> p(new objT, funcObj);