Item01:视C++为一个语言联邦
- C
- Object-Oriented C++
- Template C++
- STL
Item02:尽量以const、enum、inline替换#define
- 以编译器替换预处理器,const、enum、inline都是在程序编译阶段完成;
#define ASPECT_RATIO 1.653
const double aspectratio = 1.653;
- 宏的名称可能在编译前就被预处理器移走,并没有进入符号表,所以编译错误信息也可能仅仅提到1.653,而非ASPECT_RATIO;
- 宏可能导致盲目出现多份目标代码,常量仅一次;
class GamePlayer
{
private:
static const int NumTurns = 5; //常量声明式
int scores[NumTurns]; //使用常量
};
- C++要求你对你所使用的任何一个东西提供定义式。如果他是一个class专属静态常量,且为整数类型(int、char、bool),只要不取他们的地址,可以仅声明不定义;但是要取某个class专属常量的地址,就必须进行如下定义:
const int GamePlayer::NumTurns; //定义式,该式子应该放在实现文件中,由于class常量已经在声明时获得初值,定义时不可在设置初值。
//#define 无作用域,不能用来定义class专属常量,也不能提供封装性。
- 若编译器不允许在class声明中给初值,但又必须在编译期间告诉数组大小,可以如下做法:
class GamePlayer2
{
private:
enum { Numturn = 5 }; //令numturn成为5的符号名称
int scores[Numturn];
};
结论:
1.对于单纯常量,最好以const对象或enums替换#define
2.对于函数宏,最好用inline函数替换#defines
Item03:尽可能使用const
- const告诉编译器一个语义约束——“不能被改变”。
一、修饰
1.1 修饰global或namespace作用域中的常量
int main()
{
const int m_i = 100;
int* p = (int*)&m_i;
*p = 200;
cout << m_i << endl;
return 0;
}
1.2 修饰指针
char happy[] = "Hello";
const char* p = happy; //happy内容不可改变
char const* p = happy; //happy内容不可改变,与上条等价
p[0] = 'L'; //×
++p; //√
char* const p = happy; //指针p本身不可改变
p[0] = 'L'; //√
++p; //×
const char* const p = happy; //都不可改变
p[0] = 'L'; //×
++p; //×
- 若const出现在 * 左边,表示被指物是常量;若出现在* 右边表示指针本身是常量;
1.3 修饰迭代器
std::vector<int> m_vec;
const std::vector<int>::iterator iter = m_vec.begin(); //等价于T* const
*iter = 10; //√
++iter; //×
std::vector<int>::const_iterator iter = m_vec.begin(); //等价于const T*
*iter = 10; //×
++iter; //√
1.4 引用常量
可以有const指针,但是没有const引用(const引用可读不可改,与绑定对象是否为const无关)
**注:引用可以指向常量,也可以指向变量。例如int &a=b,使引用a指向变量b。而为了让引用指向常量,必须使用常量引用,如const int &a=1; 它代表的是引用a指向一个const int型,这个int型的值不能被改变,而不是引用a的指向不能被改变,因为引用的指向本来就是不可变的,无需加const声明。即指针存在常量指针int const p和指针常量int const p,而引用只存在常量引用int const &a,不存在引用常量int& const a。
const int& a = 12; //a只读
1.5 小tips:让函数返回一个常量值可避免意外
class Rational{};
const Rational& operator*(const Rational& rhs1, const Rational& rhs2);
Rational a,b,c;
if(a * b = c) ...
- 为了防止if中的比较操作误写为赋值操作,我们有两种方法解决:
- 1.让函数返回一个常量值,因为常量不可以被赋值;
- 2.将if的条件中的常量写在==左边;
二、const成员函数
2.1 用const修饰成员函数的意义:
- 1.得知那个函数可以改动对象,那个函数不行;
- 2.让操作const对象成为可能;
2.2 bitwise constness & logical constness
2.n tips
-
不可以同时用const和static修饰成员函数。
-
C++编译器在实现const的成员函数的时候为了确保该函数不能修改类的实例的状态,会在函数中添加一个隐式的参数const this* 。但当一个成员为static的时候,该函数是没有this指针的。也就是说此时const的用法和static是冲突的。
-
我们也可以这样理解:两者的语意是矛盾的。static的作用是表示该函数只作用在类型的静态变量上,与类的实例没有关系;而const的作用是确保函数不能修改类的实例的状态,与类型的静态变量没有关系。因此不能同时用它们。
-
Item04:确定对象被使用前已被初始化
1.C/C++中到底哪些对象在声明时会被默认初始化?
2.构造函数
- 永远在使用对象之前将其初始化,对于无任何成员的内置类型,必须手工初始化;
//内置类型
int x = 0;
const char* text = "A fww";
double d;
std::cin >> d;
- 内置类型之外的其他东西(类),初始化由构造函数完成,这里会存在一个赋值和初始化的区分;
- 以下操作未赋值而非初始化,它会先调用default构造函数然后调用拷贝赋值运算符,效率不高;
class PhoneNumber {...};
class Member
{
public:
Member(const std::string& name, cosnt std::string& address, const std::list<PhoneNumber>& phones);
private:
std::string name;
std::string address;
int numMember;
std::list<PhoneNumber> thePhones;
};
Member::Member(const std::string& na, cosnt std::string& ad, const std::list<PhoneNumber>& phones)
{
//这里是赋值而非初始化;
name = na;
address = ad;
thePhones = phones;
numMember = 50;
}
- 更好的做法是使用 成员初值列,他只调用一次拷贝构造函数;
Member::Member(const std::string& na, cosnt std::string& ad, const std::list<PhoneNumber>& phones)
:name(na),
address(ad),
thePhones(phones),
numMember(50) //成员初值列
{ }
- 需要注意的是内置类型也要写入成员初值列中;
- 当成员变量是const 或者 引用时,就一定需要初值,不能被赋值;
3.C++类中数据成员初始化顺序?
1.成员变量在使用初始化列表初始化时,与构造函数中初始化成员列表的顺序无关,只与定义成员变量的顺序有关。
2.如果不使用初始化列表初始化,在构造函数内初始化时,此时与成员变量在构造函数中的位置有关。
3.类中const成员常量必须在构造函数初始化列表中初始化。
4.类中static成员变量,只能在类内外初始化(同一类的所有实例共享静态成员变量)。
初始化顺序:
- 1) 基类的静态变量或全局变量
- 2) 派生类的静态变量或全局变量
- 3) 基类的成员变量
- 4) 派生类的成员变量