extern int x; // object 声明式
std::size_t numDigits(int number); // 函数(function)声明式
class Widget; //类声明式template //模版(template) 声明式
class GraphNode;
int x;
std::size_t numDigits(int number)
{std::size_t digitsSoFar = 1;while ((number/=10)!=0) ++digitsSoFar;return digitsSoFar;
}class Widget
{
public:Widget();~Widget();};
template
class GraphNode{GraphNode();~GraphNode();
};
explicit 声明构造函数可以防止被执行隐式类型转换(implicit type conversions),但是仍然可以用来显式类型转换。
class B
{
public:explicit B(int x = 0,bool b = true);
};
copy构造函数用来“以同类型对象初始化自我对象”,通俗的说就是复制一份。具体实现如下:
class Widget
{
public:int id;Widget(int id);public:Widget();~Widget();Widget(const Widget& rhs);Widget& operator=(const Widget &rhs);void printWidget();
};Widget::Widget()
{}Widget::~Widget()
{}
Widget::Widget(int id) : id(id) {}Widget::Widget(const Widget &rhs):id(rhs.id)
{ //copy构造}Widget &Widget::operator=(const Widget &rhs)
{ //copy assignment 操作符id = rhs.id;return *this;
}
copy构造函数是一个重要的函数,它定义了一个对象如何passed by value
通常来说以by value 传递用户自定义类型通常不是很好的选择,pass by reference to const 是更好的选择。
命名习惯方面 lhs 与 rhs 为二元操作符(binary operators)
TR1 (Technical Report 1) 是一份规范,描述加入c++标准程序库的诸多新机能。
Boost开源库。
当你从一个次语言切换到另一个时候,会导致高效守则的改变,比如说,当你使用内置类型(C-like)的时候,pass-by-value通常比pass-by-reference更高效,但是当你从*C part of C++移往Object-Oriented C++*的时候,由于用户自定义构造函数和析构函数,pass-by-reference往往更好
这个的意思是,尽可能多的让编译器替换预处理器。因为预处理器会根据宏定义的内容,在源代码中直接盲目替换,有可能会导致未知的错误发生。
#define ASPECT_RATIO 1.653
当你使用如上的宏的时候,可能会发生:在编译器处理的时候,记号__ASPECT_RATIO__就被预处理器移走了,所以记号__ASPECT_RATIO__有可能没进入记号表内,于是当你使用这个变量的时候就会获得一个编译错误信息,而且这个错误信息的内容是1.653,这会让你不明所以,解决之道便是使用__const__:
const double AspectRatio = 1.653
这样写还有一个好处是,防止预处理器盲目地把宏名替换,导致记号出现多份。
为了被不同的源码所包含,所以常量通常被放在头文件内,因此,如果是指针,那么有必要把指针声明为const,例如:
如果要在头文件内定义常量的char字符串,必须得写const*两次
const char *const authorName = "tom";
当然了,咱们是学C++不是C,所以定义成string显然更好
const std::string authorName("tom");
关于const修饰,是指针不变还是指针所指之物不变可见条款3:尽可能多使用const
为了将常量的作用域限制于类内,则这个常量就必须成为这个类的成员,而为了确保这个成员只有一份,所以,必须得让它成为一个static成员,例如:
class A
{
private:static const int numTurns = 5; // class 类中专属的常量
};
但是你看到numTurns的是声明,而不是定义。C++要求我们对我们所使用的任何东西提供一个定义,但是如果是类专属常量并且又是static并且是整数类型(int, char, bool),则需要特殊处理。
如果,定义了类内静态成员,需要留意一些其他问题,可以参考C++primer第七章第6小节
C++primer第七章
万万不可用#define 创建 类专属变量,因为#define并不重视作用域,也就是说,一旦被定义,那么这之后都有效,除非后面#undef
所以,#define也不能提供封装性,也就是说,并没有private #define,但是const可以
假设有这么一段代码
class GamePlayer
{
private:static const int numTurns = 5; // 声明一个class 类中专属的常量。int wins[numTurns];
};
在编译期间,需要知道数组大小,所以在编译期间就需要知道常量的值,但是有一些老版本的编译器可能不支持对static整数型class常量赋初值,那么可以采用the enum hack,其理论基础是:”一个属于枚举类型的数值可以权充int使用“,于是GamePlayer可以被定义如下:
class GamePlayer
{
private:enum {NumTurns = 5};int wins[numTurns];
};
基于以上代码,我们可以认识到:the enum hack有点类似#define,而不是const,例如:
获取一个const地址是合法的,但是取一个enum地址是非法的,当然取#define也是非法的
宏函数看上去像是函数,但是不会招致函数调用带来的额外开销,假如有如下代码
#define MAX(a, b) f((a) > (b) ? (a) : (b))
不论何时,当你使用宏函数的时候,切记给函数的参数加上小括号,理由大家肯定都知道,但是即使你加上了
还会有奇奇怪怪的情况发生,假设有如下代码:
int a = 5, b = 10;
MAX(++a, b); // a被累加二次
MAX(++a, b + 10); // a被累加一次
这段代码可以看出,a的累加次数居然取决于b的数值
所以,解决方法就是使用template inline函数
template
inline void callWithMax(const T&a, const T&b)
{f(a > b ? a : b);
}
该函数会比较出大者,然后调用函数f,此外,由于callWithMax是一个真正的函数,所以它遵守作用域和访问规则,例如我们可以实现class内的private inline函数,而宏做不到
char greet[] = "hello";
const char *p = greet; // non-const pointer ,const data
char* const p1 = greet; // const pointer, non-const data
const char* const p2 = greet; // const pointer, const data
这一部分内容,大家可以配合C++primer 5th的第二章第四小节
C++primer 5th 第二章要点总结
关于const修饰指针,只需要牢记const和星号的位置关系即可
如果关键字const出现在星号左边,表示所指物是常量;如果出现在星号右边,表示指针自身是常量,如果在星号两边都出现了const则表示指针和指物都是常量。
STL迭代器是以指针为根据塑造出来,所以迭代器的作用就像T指针。
声明迭代器为const就像声明指针为const一样,即声明一个T*const*指针,表示这个指针不可以再指向其他的东西,但是指向的东西可以改变
如果你想要迭代器指向的东西不能够改变,则需要使用const_iterator
// T* const 可以改变iter所指的值,但是不能改变iter指针。
const std::vector::iterator iter = vec.begin(); // const *T 不可改变cIter所指的值,但是可以改变iter的指针。
std::vector::const_iterator cIter = vec.begin();
为了避免 (a*b)= c; 的情况发生。当然这样的错误应该避免。
使用const 令重载的操作符返回一个const的值就避免了上述不必要的情况发生。
class Rational
{
public:const Rational operator*(const Rational& lhs,const Rational& rhs);
};const Rational Rational::operator*(const Rational &lhs, const Rational &rhs) {return Rational();
}
将const实施于成员函数的目的,是为了确认该成员函可以作用于const对象身上。基于两个理由:
假设有以下的类,表示一大块文字:
class TextBlock
{
private:std::string text;public:const char& operator[](std::size_t position) const;
};const char &TextBlock::operator[](std::size_t position) const
{return text[position];
}
上面const成员函数的实现,是返回一个const值且text[] 成员在函数中不可改变。
class TextBlock
{
private:std::string text;
public:char& operator[](std::size_t position);
};char& TextBlock::operator[](std::size_t position)
{return text[position];
}
此种实现,不同与前者,此种返回的值可以改变,并且,成员变量的内容在重载操作符中的可以被改变。
值得注意的是,不管是不是const返回的都是引用,如果的返回的不是引用,那么遇到如下代码时:
text[0] = 'x';
如果函数的返回类型是内置类型,那么就无法通过编译,因为这段代码企图更好一个函数的返回值
退一步讲,即使它是合法的,那更改的也只是一个副本,而不是**text[0]**本身
const前置声明函数返回const值,后置声明保证了成员变量的稳定性,即在函数执行的过程中不被改变,这就是 bitwise const原则。
const 成员函数,保证了成员变量在函数实现中不能改变 ,但是有时候需要改变成员变量,此时用一个与const相关的 mutable(可变的) 修饰成员变量来实现。
class CTextBlock
{
private:char *pText;mutable std::size_t textLength;mutable bool lengthIsValid;
public:char* getpText(){return pText;}CTextBlock();CTextBlock(char *pText);std::size_t length() const;
};std::size_t CTextBlock::length() const
{cout<
使用成员变量的时候确保已经初始化__ (编译器不会为你做初始化的工作!) 上述代码在测试的过程中,由于自身疏忽,如果构造函数中没有对成员变量初始化时,成员变量的值为随机数,因此不安全会出现很奇怪的错误,为了规范编程的要求,避免不必要的错误发生,在构造函数中一定要初始化。
例如
class TextBlock
{
public:static const int Num = 30;char text[Num];
public:char& operator[](std::size_t position) ;const char& operator[](std::size_t position) const;
};char& TextBlock::operator[](std::size_t position)
{text[position]++;//边界检验的代码(bounds checking)...//志记数据访问的代码(log access data)...//检验数据完整性的代码(verify data integrity)...return text[position];
}
const char &TextBlock::operator[](std::size_t position) const
{//text[position]++;//边界检验的代码(bounds checking)...//志记数据访问的代码(log access data)...//检验数据完整性的代码(verify data integrity)...return text[position];
}
在上述代码中,const 和 non-const 的函数实现中都出现了,同样的内容,在规模较大的项目中,编译时间过长,维护,代码膨胀都是令人很头疼的问题。解决上述问题的方式就是常量转型 (casting away constness)。具体实现如下:
class TextBlock
{
public:static const int Num = 30;char text[Num];
public:char& operator[](std::size_t position) ;const char& operator[](std::size_t position) const;
};char& TextBlock::operator[](std::size_t position)
{//将op[]返回值的const转除return const_cast( static_cast(*this)[position] //先为*this加上const,然后调用const_cast再去除const );}
const char &TextBlock::operator[](std::size_t position) const
{//text[position]++;//边界检验(bounds checking)...//志记数据访问(log access data...//检验数据完整性(verify data integrity)...return text[position];
}
上面代码可能有一些难以理解,如果非const只是单纯调用operator[]的话,那么就是递归调用自己,这样将会陷入死循环,所以需要特别指明是调用const版本,所以就有了以上的代码
Singleton 模式
上述实现过程中有两个转型操作:
注意:反向操作是不允许的:令const版本调用 non-const版本。因为const成员函数承诺绝不改变其对象的逻辑状态(logical state)。
为了避免随机初始化的值,导致不可测知的程序行为。我们应当在对象,变量在使用之前保证其已经被初始化。这样初始化的责任就落到了构造函数(constructors)
构造函数的初始化分为 赋值(assignment) 和 初始化(initialization) 通常来说在构造函数中使用 成员初值列(member initialization list) 才是初始化,在构造函数函数体内就是赋值了。
假设有如下实现通讯录的代码:
class PhoneNumber
{...
};class ABEntry
{
private:std::string theName;std::string theAddress;std::list thePhones;int numTimesConsulted;
public:ABEntry();ABEntry(const std::string &name, const std::string &address,const std::list &phones);
};ABEntry::ABEntry(const std::string &name, const std::string &address,const std::list &phones): theName(name), theAddress(address), thePhones(phones), numTimesConsulted(0) {}ABEntry::ABEntry() : theName(), theAddress(), thePhones(), numTimesConsulted(0)
{ }
上述代码是在构造函数中用 成员初值列(member initalization list) 的方法去完成初始化的工作。相比如果用简单的赋值操作进行初始,这样的效率要高。
原因在于:赋值初始化,要经过default 构造函数,然后调用 copy assignment ,而成员初值列是单次调用copy 构造函数效率要高。
在上述代码中 default 构造中对numTimesConsulted 成员做了显式的初始化。
注:在成员初值列中初始化的顺序最好和 内置型对象的声明 顺序一致,因为C++中,是按照声明顺序初始化的
所谓static 对象,其寿命从被构造出来直到程序结束为止,因此 stack 和 heap-based对象都被排除。
编译单元 (translation unit) :是指产出单一目标文件(single object file)的那些源码。基本上它是单一源码文件加上它所含入的头文件。
问题:多个源码文件,每一个至少一个non-local static。当某个源码文件中的non-local static 的动作使用了另一个编译单元中的的某个对象,但是这个对象并 未初始化 因此出现问题。
class FileSystem
{
public:std::size_t numDisk() const;
};std::size_t FileSystem::numDisk() const
{return 0;
}
extern FileSystem tfs;
FileSystem& tfs1(){ //这个函数用来替换tfs对象static FileSystem fs; //它在FileSystem class中可能是个static。return fs; //定义并初始化一个local static对象 并返回一个reference指向上述对象。
}
class Directory
{
public:Directory();};Directory::Directory()
{std::size_t disks = tfs1().numDisk();
}
extern Directory tempDir;
Directory& tempDir1(){static Directory td; //同上return td;
}
解决方法如上所示。Singleton 模式。
C++保证,函数内的local static对象会在“该函数被调用期间” “首次遇上该对象之定义式”时被初始化。
注意:任何一种non-const static对象,不论它是local还是non-local,在多线程的环境下“等待某种事情发生”都会有麻烦,处理这个麻烦的做法是: 在程序单线程启动阶段(single-threaded startup portion )手工调用所有reference-returning函数(也就是上述代码实现),这可以消除与初始化有关的“竞速形势”(race conditions)。
const 对于c++规范编程,效率方面十分重要。需要熟练掌握。第1章的学习笔记结束,本人会不断更新之后的学习笔记