class Widget{
public:
Widget(); //构造函数
Widget(const Widget& rhs); //拷贝构造函数
Widget& operator=(const Widget& rhs); //重载“=”号
~Widget();
}
Widget w1; //1、构造函数
widget w2(w1); //2、拷贝构造函数
w1 = w2; //3、调用重载
Widget w3 = w2; //4、调用拷贝构造函数
【注】看第4个,因为w3是对象实例化,故肯定会调用构造函数,不可能是赋值操作;同理第3个,两者均为实例化对象,此时调用的是重载后的赋值操作。
bool hasAcceptableQuality(Widget w);
...
Widget aWidget;
if (hasAcceptableQuality(aWidget))...
参数w是按值传递,所以上述aWidget被复制到w的体内,这个复制动作是通过拷贝构造函数实现的;通常使用按值传递是个不好的行为,一般会选择引用进行传递。
int *p = 0; //p是个null指针
std::cout<<*p; //对一个null指针取值,会导致不明确行为
char name[] = "Darla"; //name是个数组,大小为6(别忘记最尾端的"/0")
char c = name[10]; //指向一个无效的数组索引,导致不明确行为
尽量以const、enum、inline替换#define
第一章:
关于const:
class专属常量:为了将常量作用域限制于class内,必须让它成为class的一个成员(member),而为确保此常量至多只有一份实体,你必须让它成为一个static成员。
class GamePlayer{
private:
static const int NumTurns = 5;
int scores[NumTurns];
...
}
上述代码中NumTurns是一个声明式而非定义式,但如果它是个class专属常量又是static且为整数类型,则需要特殊处理:
- 只要不取它们的地址,可以声明并使用它们而无需提供定义式
- 如果取某个class专属常量的地址,或编译器坚持看到一个定义式,必须提供如下定义式:
- const int GamePlayer::NumTurns;
- 请把该式子放进一个实现文件而不是头文件,同时因为之前赋值了5,因此定义时不可以再赋初值
部分编译器不允许static变量在类内进行声明,因此这个时候可以选择枚举类型充当int使用,例:
class GamePlayer{
private:
enum{NumTurns = 5};
int scores[NumTurns];
...
}
【注】有时候enum类似于#define,当取const类型的地址是合法的,但是取enum和#define的地址是不合法的
关于define:
常见的#define误用情况是以它实现宏(macros)。宏看起来像函数,但不会招致函数调用带来的额外开销。例:
- #define CALL_WITH_MAX(a,b) f((a)>(b)?(a):(b))
但是这样会有很多缺点,例如:
int a = 5,b = 0;
CALL_WITH_MAX(++a,b); //a被累加二次
CALL_WITH_MAX(++a,b+10); //a被累加一次
【注】1、对于单纯常量,最好以const对象或者enums替换#define
2、对于形似函数的宏(macros),最好改用inline函数替换#define
尽可能用const
const语法变化多端,如果关键字const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身的常量。
const char* p = greeting; //非常量指针,常量数据
char* const p = greeting; //常量指针,非常量数据
如果被指物是常量,可将关键字const写在类型之前,也可写在类型之后,星号之前,两者意义相同。
STL迭代器以指针为模型进行构造,迭代器的作用就像个T*指针。声明迭代器为const和声明指针为const一样,表示这个迭代器不能指向不同的东西,但它所指的对象的值是可以改动的。如:
std::vector<int> vec;
...
const std::vector<int>::iterator iter = vec.begin(); //iter的作用像个T* const
*iter = 10; //指针指向对象可以改变
++iter; //错误!iter是const
std::vector<int>::const_iterator citer = vec.begin(); //iter的作用像个const T*
*citer = 10; //错误!*citer是const
++citer; //没问题,改变citer
const成员函数
目的:为了确认该成员函数可作用于const对象。
理由:1、使class接口比较容易被理解,可以知道哪个函数可以改动对象内容而别的不可以。
2、使“操作const对象”成为可能。
确定对象被使用前已先被初始化
如:
class Point{
int x,y;
}
...
Point p;
在某些语境下x保证被初始化(为0),但在其他语境中却不保证;p的成员变量有时候初始化(为0),有时候不会;而读取未初始化的值,就可能让你的程序终止运行。
【最佳处理办法】永远在使用对象前将其初始化。
int x = 0;
const char* text = "A C-style string";
double d;
std::cin>>d;
class PhoneNumber{.....};
class ABEntry{
public:
ABEntry(const std::string& name,const std::string& address,const std::list<PhoneNumber>& phones);
private:
std::string theName;
std::string theAddress;
std::list<PhoneNumber> thePhones;
int numTimesConsulted;
}
ABEntry::ABEntry(const std::string& name,const std::string& address,const std::list<PhoneNumber>& phones){
theName = name; //这些都是赋值(assignments)
theAddress = address; //而非初始化(initializations)
thePhones = phones;
numTimesConsulted = 0;
}
这样可以使对象带有你期望的值,但不是最佳做法。C++规定,对象成员变量的初始化动作发生在进入构造函数本体之前。初始化的发生时间更早,发生于这些成员的default构造函数被自动调用之时(比进入构造函数本体时间更早),故而比较好的写法如下:
//这样效率更高
ABEntry::ABEntry(const std::string& name,const std::string& address,const std::list<PhoneNumber>& phones):theName(name),theAddress(address),thePhones(phones),numTimesConsulted(0){}
不同单元内定义之non-local static对象的初始化次序:
函数内的static对象称为local static对象,其他的统称为non-local static对象。程序结束时,static对象会被自动销毁,它们的析构函数会在main()结束前被自动调用;常见的问题:某编译单元内的某个non-local static对象初始化动作使用了另一编译单元内的某个non-local static对象,它所使用的这个对象可能尚未被初始化,如:
class FileSystem{
public:
...
std::size_t numDisks() const;
...
}
extern FileSystem tfs; //预备给客户使用的对象
//tfs代表“the file system”
如果客户在FileSystem对象构造完成前就使用它,后果不堪设想。
假设某些客户建立了一个class处理文件系统内的目录,很自然的就使用上了FileSystem对象:
class Direstory{
public:
Directory(param);
...
};
Directory::Directory(param){
...
std::size_t disks = tfs.numDisks(); //使用了tfs对象
...
}
进一步假设,客户决定创建一个Directory对象,用来放置临时文件:
Directory tempDir(param); //为临时文件而做出的目录
现在,初始化次序重要性出来了,除非tfs在tempDir之前先被初始化,否则tempDir的构造函数会用到尚未初始化的tfs;这时,我们无法确定是否初始化。
但是却可以通过小小的一个设计消除这个对象:将每个non-local static对象搬到自己专属函数内,该对象在函数内被声明为static,这些函数返回一个reference(引用,参考)指向它所含的对象;然后用户调用这些函数,而不直接指涉这些对象(即通过函数调用方式确保non-local static对象被初始化)。
class FileSystem{...};
FileSystem& tfs(){
static FileSystem fs;
return fs;
}
class Directory{...};
Directory::Directory(params){
...
std:size_t disks = tfs().numDisks();
...
}
Directory& tempDir(){
static Directory td;
return td;
}