1.声明式(declaration): 告诉编译器某个东西的名称和类型,但略去细节;每个函数的声明揭示其签名式(signature),也就是参数和返回类型。
extern int x;
std::size_t numDigits(int number);
template <typename T>
class GraphNode;
2.定义式(definition): 的任务是提供编译器一些声明式所遗漏的细节。对 对象而言,定义是是编译器为此对象拨发内存的地点。。对function或function template而言,定义是提供了代码本体。对class 或class template 而言,定义式列出他们的成员;
int x;
std::size_t numDigits(int number){
std::size_t digitsSoFar = 1;
while((numer /= 10) != 0) ++digitsSoFar;
return digitsSoFar;
}
templateT>
class GraphNode{
public:
GraphNode();
~GraphNode();
//...
};
3.初始化(initialization) 是“给予对象初值”的过程,对用户自定义类型的对象而言,初始化由构造函数执行。
class A{
public:
A(); //default constructor
};
class B{
public:
explicit B(int x = 0, bool b = true); //default constructor
};
class C{
public:
explicit C(int x); // not default constructor
};
//example
void doSomething(B bObject);
B bObject1;
doSomething(bObject1); //true
B bObject1(28); //true
doSomething(28); //fault, from int to B ,no implicit type conversions
doSomething(B(28)); //true ,explicit type conversions.
explitcit 可以阻止执行隐式类型转换,但是还是可以进行显示类型转换。
被声明为explicit的构造函数通常比其non-explicit更受欢迎,因为他们禁止编译器进行非预期的类型转换。
4.copy constructor function 被用来“以同类型对象初始化自我对象”, copy assignment operator 被用来“从另一个同类型对象拷贝其值到自我对象”:
class Widget{
public:
Widget();
Widget(const Widget & rhs);
Widget& operator=(const Widget& rhs);
//...
};
Widget w1; //default constructor
Widget w2(w1); //copy constructor function
w1 = w2; // copy assignment operator
Widget w3 = w2; //copy constructor
区分copy constructor 和 copy assignment的区别,如果对象有一个新对象被定义,一定会有copy constructor called,不可能调用copy assignment function,如果没有新对象被定义,就不会有constructor函数被调用,应该是copy assignment function called。
copy constructor 定义一个对象如何passed by value:
bool hasAccepttableQuality(Widget w);
//...
Widget aWidget;
if(hasAccepttableQuality(aWidget)) //...
w是以by value方式传递给hasAccepttableQuality,所以aWidget被复制到w体内。复制动作是有copy constructor完成的。passed by value意味调用copy constructor。passed by reference to const是一个较好的选择。
5. undefined behavior
int *p = NULL;
std::cout<<*p; // undefined behavior
char name[] = "Darla";
char c = name[10]; //undefined behavior
c++已经是一个多重范型编程语言,同时支持:过程形式(procedural), 面向对象形式(object-oriented), 函数形式(functional), 泛型形式(generic), 元编程形式(metaprogramming) 的语言。在某个此语言中,各种守则与通例都倾向简单,直观易懂,并且容易记住。
1. C。C++任是以C为基础。 blocks, statements, preprocessor, built-in data types, arrays, pointers 来自c。但是c没有template,exceptions,overloading等特性。
2. object-oriented c++。此部分包括:class, 封装(encapsulation), 继承(inheritance), 多态(polymorphism), virtual函数(动态绑定)。。。
3. template c++。 template metaprogramming。
4. STL。对容器(containers), 迭代器(iterator), 算法(algorithm)以及函数对象(functions objects) 的规约有极佳的紧密配合与协调。
对内置类型而言,pass-by-value通常比pass-by-reference高效,但是对于用户自定义类型而言pass-by-reference-to-const往往更好。对于STL而言,迭代器和函数对象是在C指针上塑造出来的,所以对于STL的迭代器和函数对象而言,旧式的pass-by-value再次适用
const char * const authorName = "Scott Meyers";
const std::string authorName("Scott Meyers"); //string 对象通常比char*-based合适
class GamePlayer{
private:
static const int NumTurns = 5; //const declaration 而非定义
int scores[NumTurns];
//...
};
//只要不取它的地址,可以声明使用它,不许提供定义
const int GamePlayer::NumTurns; // 但是如果取地址,或编译器必须看到定义时,使用此方法定义
//因为声明的时候已经获得了初值,所以定义的时候不可以再设初值。
//如果上述方法编译器不支持,可以使用下面方法
class CostEstimate{
private:
static const double FudgeFactor; //头文件内
//...
};
const double CostEstimate::FudgeFactor = 1.35; //实现文件内
//如果编译器不允许static整数型class常量完成in class 初始值设定,可以改用the enum hack做法
class GamePlayer{
private:
enum{NumTurns = 5};
int scores[NumTurns];
//...
};
const是一个语义约束,编译器会强制实施这个约束。const出现在*号左边,表示被指物是常量,如果出现在*右边,表示指针自身是常量,出现在两边,表示被指物和指针两者都是常量。
std::vector<int> vec;
cosnt std::vector<int>::iterator iter = vec.begin(); //iter 的作用像个 T* const
*iter = 10; //true
++iter; //false iter is const
std::vector<int>::const_iterator cIter = vec.begin(); //cIter的作用像个const T*
*cIter = 10; //false
++cIter; //true
class Rational{//...};
const Rational operator*(const Rational& lhs, const Rational &rhs);
Rational a,b,c;
(a*b) = c;// false, 使用const修饰返回值,可以避免此操作
class TextBlock{
public:
const char& operator[] (std::size_t position) const{
return text[position];
}
char & operator[] (std::size_t position){
return text[position];
}
private:
std::string text;
};
TextBlock tb("hello");
std::cout<0];
const TextBlock ctb("world");
std::cout<0];
tb[0] = 'x'; //true
ctb[0] = 'x'; false
如果non-const operator[] 的返回类型是一个reference to char,不是char。如果返回一个char,下面的代码无法通过编译tb[0] = 'x';
,因为函数的返回类型是一个内置类型。改动返回值重来就不合法。即使合法c++以by value返回对象这一个事实,意味着被改动的是他的一本副本,不是自身,不是我们想要的行为。
3. bitwise const:成员函数只有在不更改对象的任何成员变量时才可以说是const。编译器只需寻找成员变量的赋值动作即可。不幸的是,许多成员函数虽然不十足具备const性质却能通过bitwise测试。
class CTextBlock{
public:
char & operator[] (std::size_t position)const{
return pText[position];
}
private:
char * pText;
};
//operator[] 不改变pText,可以通过编译器的bitwise检测
const CTextBlock cctb("hello");
char *pc = &cctb[0];
*pc = 'J'; //cctb现在变成了Jello
上面这种情况,不应该出现错误,但是却改变了他们的值。因此提出了logical const
logical const:一个const成员函数可以修改它所处理的对象内的某些bits,但只有在客户端侦测不到的情况下才得如此。
class CTextBlock{
public:
std::size_t length() const;
private:
char *pText;
std::size_t textLength;
bool lengthIsValid;
};
std::size_t CTextBlock::length()const{
if(!lengthIsValid){
textLength = std::strlen(pText);
lengthIsValid = true;
}
return textLength;
}
length的实现当然不是bitwise const,因为里面有值的改变。但是对于const CTextBlock对象是可以接受的。但编译器不同意,它们坚持bitwise constness。可以通过mutable来释放掉non-static成员变量的bitwise constness的约束。在变量textLength和lengthIsValid前面加上mutable。
4. 在const和non-const成员函数中避免重复。
class TextBlock{
public:
const char& operator[](std::size_t position) const{
//...边界检测
//...数据访问,检测数据完整性
return text[position];
}
char &operator[](std::size_t position){
//...边界检测
//...数据访问,检测数据完整性
return text[position];
}
private:
std::string text;
};
//代码重复,可以通过常量性转除
//例如:
char &operator[](std::size_t position){
return const_cast(static_case(*this)[position]);
//为了明确指出调用的是const operator[],这里将*this,从原始的TextBlock&转为const TextBlock&,第二次这是从const operator[]的返回值中移除const.
}
不应该用const版本调用non-const版本来避免重复,const成员函数承诺绝不改变对象的逻辑状态,但是non-const成员函数却没有这样的承诺。
1.最佳的处理方法:永远在使用对象之前将它初始化,对于无任何成员的内置类型,你必须手动完成此事。
int x = 0;
const char *text = "A C-style string";
double d;
std::cin>>d;
对于内置类型以外的任何其他东西,初始化责任落在构造函数身上。规则很简单:确保每一个构造函数都将对象的每个成员初始化.
不要混淆了赋值(assignment)和初始化(initialization).
class PhoneNumber{//...};
class ABEntry{
public:
ABEntry(const std::string &name, const std::string &address, const std::list &phones);
private:
std::string theName;
std::string theAddress;
std::list thePhones;
int numTimesConsulted;
};
ABEntry::ABEntry(const std::string &name, const std::string &address, const std::list &phones){
theName = name; //赋值操作不是初始化
theAddress = address;
thePhones = phones;
numTimesConsulted = 0;
}
c++规定对象的成员变量的初始化动作发生在进入构造函数之前。初始化应该发生在成员的default构造函数被自动调用之时(比进入构造函数本体的时间更早)
规定总是在初值列中列出所有成员变量,以免还得记住哪些成员变量可以无需初值,如果遗漏了内置类型的初始化,可能开启不明确行为的潘朵拉盒子。
2. 初始化对象的顺序不确定时的处理方法,(不同编译单元内定义的non-local static对象的初始化次序)
问题;某个编译单元内的某个non-local static对象的初始化动作使用了另一个编译单元内的某个non-local static对象,他所用到的这个对象可能尚未被初始化。
class FileSystem{
public:
std::size_t numDisks() const;
};
extern FileSystem tfs; //预备给客户用的对象
class Directory{
public:
Director();
//...
};
Director::Director(params){
std::size_t disks = tfs.numDisks(); //使用tfs对象
}
Director tempDir(params); //如果tfs在tempDir之前未被初始化,会出现未定义行为
//并且tfs和tempDir的初始化过程先后次序没有明确定义的。
解决这个问题的方法是将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static),然后返回一个reference指向它所含的对象。然后用户调用这些函数,而不是直接指向这些对象。
C++保证,函数内的local static对象会在函数被调用期间,首次遇上该对象的定义式时被初始化。
class FileSystem{//...};
FileSystem& tfs(){
static FileSystem fs;
return fs;
}
class Director{//....};
Director::Director(params){
std::size_t disks = tfs().numDisks();
}
Director& tempDir(){
static Directory td;
return td;
}
这种结构下的reference-returning函数往往十分单纯:第一行定义并初始化一个local static对象,第二行返回它。但是这样做不是线程安全的。
任何一种non-const static对象,不论他是local或non-local的,在多线程环境下“等待某事发生”都会有麻烦,解决方法:在程序的单线程启动阶段手动调用所有reference-returning函数,可以消除与初始化有关的“竞速形势”。