首页 > 代码库 > C++必知必会(1)
C++必知必会(1)
条款1数据抽象
抽象数据类型的用途在于将变成语言扩展到一个特定的问题领域。一般对抽象数据类型的定义须要准训下面步骤:
1. 为类型取一个描写叙述性的名字
2. 列出类型所能运行的操作
3. 为类型设计接口
4. 实现类型
条款2多态
多态类型,从基类继承的最重要的多系就是它们的接口,而不是它们的实现。
条款3设计模式
条款4 STL
STL优秀思想体如今:容器与在容器上运行的算法之间无需彼此了解。这样的戏法是通过迭代器实现的。STL容器和算法分别通过类模板和模板函数实现。
条款5引用时别名而非指针
引用必须被初始化,一个引用即使在该引用被初始化之前已经存在的一个对象的别名。
一个指向很量的引用是不能够用字面值或暂时值进行初始化的。
而一个指向常量的引用就能够。当一个指向常量的引用採用一个字面值来初始化时,该引用实际上被设置为指向採用该字面值初始化的一个暂时位置。
引用和指针的差别:
1.不存在空引用
2.全部引用都须要初始化
3.一个引用永远指向用来对它初始化的那个对象
条款6数组形參
事实上C/C++中根本不存在所谓的数组形參,由于数组在传入时,实质之传入指向其首元素的指针。数组被作为性參数会丢失边界。
void average(intary[]); 与 void average(int ary[12]);作用一样。
还有一方面,假设数组边界的精确数值很重要。而且希望函数仅仅接受含有特定数量元素的数组,能够考虑使用一个引用形參:
void average(int(&art)[12]); //如今函数仅仅能接受大小为12的整形数组
模板有助于代码的泛华:
template<intn>
void average(int(&ary)[n]); //让编译器帮我们推导n的值
只是,更传统的做法是将数组的大小明白的传入函数:
void average(intary[], int size);
当然我们能够将这两种方法结合起来
template<intn>
inline voidaverage(int (&ary)[n])
{
average(int ary, n);
}
为保证数组边界不丢失,数组的大小必须以形參的方式显示的编码,并以单独的实參传入或者在数组内部以一个结束符值作为指示(比如用于指示“用作字符串的字符数组”的末尾’\0’)。无论数组是怎样声明的,一个数组一般是通过指向其收元素的指针进行操作的。
因为这些原因。常常採用某种容器(vector或string)来替换数组的大多数传统使用方法。
多维数组是数组的数组,因此形參是一个指向数组的指针。
void process(int(*ary)[20]); //一个指针,指向一个具有20个int元素的数组
注意第二个边界没有退化,否则无法对形參运行指针算术。
对于多维数组形參的有效处理例如以下:
void process(int*a, int n, int m)
{
for(int i=0; i<n; ++i)
for(int j=0; j<m; ++j)
{
a[i*m+j] = 0; //手工计算索引
}
}
相同,有事模板有助于让事情更干净利落:
templat<intn, int m>
inline voidprocess(int (&ary)[n][m])
{
process(&arr[0][0], n, m);
}
条款7常量指针和指向常量的指针
const T* pct = pt; //一个指向const T的指针
T* const cpt = pt; //一个const指针,指向T
能够将一个指向很量的指针转换为一个指向常量的指针。但不能将指向常量的指针转化为指向很量的指针。
条款8指向指针的指针
有两种情况会看到指向指针的指针:
1. 声明指针数组时,因为数组名会退化为指向其首元素的指针。所以指针数组的名字也是一个指向指针的指针。
2. 当一个函数须要改变传递给它的指针的值时。
通常,C++中秀安使用指向指针的引用作为函数的參数,而不是指向指针的指针作为參数。
一个常见的误解是:适用于指针的转化相同适用于指向指针的指针。事实并不是如此。例如以下:
Circle* c = new Circle;
Shape* s = c; //正确
Circle **cc = new Circle;
Shape** ss = cc; //错误
由于Circle是一个Shape。因而一个指向Circle的指针也是一个Shape指针。然后,一个指向Circle指针的指针并非一个指向Shape指针的指针。
当设计const相同会发生混淆。我们知道将一个指向很量的指针转化为一个指向常量的指针是合法的。但不能够将一个指向“指向很量的指针”的指针转化为一个“指向常量的指针”的指针。
char* s1 = 0;
const char* s2 =s1; //没问题
char* a[MAX]; //也就是char**
const char ** ps= a; //错误。
条款9新式转型操作符
const_cast操作符同意加入或移除表达式中类型的const或volatile修饰符。
static_cast操作符用于相对而言可跨平台的转型。
最常见的情况是,它用于将一个继承层次结构中的基类的指针或引用。向下转型为一个派生类的指针或引用。
reinterpret_cast从位的角度来对待一个对象,从而同意将一个东西看做还有一个全然不同的东西。
dynam_cast通经常使用于运行从指向基类的指针安全地向下转型为指向派生类的指着。不同static_cast,dynamic_cast仅用于对多态进行向下转型(也就是说,被转型的表达式的类型,必须是一个指向带有虚函数的类类型的指针),而且运行运行期检查工作,来推断转型的正确性。
条款10常量成员函数的含义
在类X的很量函数中,this指针的类型为X*const。
也就是说,它指向很量X的常量指针。因为this指向的对象不是常量,因此它能够被改动。而在类X的常量成员函数中,this的类型为const X* const。也就是说,是指向常量X的常量指针。因为指向的对象是常量。因此它不能被改动。
类的非静态数据成员能够说声明为mutabl,这将同意它们的值能够被该类的常量成员函数改动,从而同意一个逻辑上为常量的成员函数被声明为常量,尽管事实上现须要改动该对象。
一个成员函数的常量版本号和很量版本号能够重载。
常量对象调用常量版本号。
条款11编译器会在类中放东西
假设类声明了一个或多个虚函数,编译器会为该类插入一个指向虚函数表的自指针。
有时使用了虚拟继承,即便类没有虚函数,当中还是有可能被插入一个虚函数表指针。
由于跨平台的内存布局不同的原因,不要使用memcpy这种标准内存块复制函数,而应该使用对象的初始化或赋值操作。
条款12赋值和初始化并不同样
赋值发生于赋值时,除此之外。所遇到其它的复制情形均为初始化,包含声明、函数返回、參数传递以及捕获异常中的初始化。
初始化操纵:先分配内存用于容乃字符串的复制,然后运行复制操作
赋值有一点像析构动作后跟一个构造动作。
条款13复制操作
复制构造函数和赋值函数总是成对的声明。
复制构造函数被声明为X (const X &),而复制赋值操作应被声明为&operat = (const X &)。
条款14函数指针
能够声明一个指向特定类型函数的指针:
void (*fp)(int); //指向函数的指针
它表示fp是一个指向返回值为void的函数的指针。就像指向数据的指针一样,指向函数的指针也能够是空,否则它就应该指向一个具有适当类型的函数。
extern void h(int);
fp = h; //正确
fp = &h; //正确
将一个函数的地址初始化或赋值给一个函数的指针时,无需显式地取得函数地址。编译器知道隐式地获取函数的地址。因此在这样的情况下&操作符时可有可无的。
类似地,为了调用函数指针所指向的函数而对指针进行解引用操作也是不必要的。由于编译器能够帮你解引用。例如以下:
(*fp)(12); //显示解引用
fp(12); //隐式地解引用,结果同样
和void*指针能够指向不论什么类型的数据不同,不存在能够指向不论什么类型函数的通用指针。还要注意,非静态成员函数的地址不是一个指针,因此不能够将一个函数指针指向一个非静态成员函数。
函数指针的一个传统用途是实现回调。一个回调就是一个可能的动作,这个动作在初始化阶段设置。以便对将来可能发生的事件做出反应时而被调用。
一个函数指针指向内联函数时合法的,然而通过函数指针调用内联函数将不会导致内联式的函数调用(也就是不会将代码展开),由于编译器通常无法在编译器精确的确定会调用什么函数。
因此在调用点,编译器别无他法,仅仅好生成间接、非内联的函数调用代码。
条款15指向类成员的指针并不是指针
一个常规指针包括一个地址。假设解引用,就会得到位于该地址的对象。
与常规指针不同,一个指向成员的指针并不指向详细的内存位置。它指向的是一个类的特定成员,而不是指向一个特定对象里的特定成员。
通常最清晰的做法是将指向数据成员的指针看做是一个偏移量。但C++标准没有这样规定,仅仅是大多数编译器都将指向数据成员的指针实现为一个整数,当中包括被指向的成员的偏移量。另外加上1(加1是为了让值0能够表示一个空的数据成员指针。)这个偏移量告诉你。一个特定成员的位置距离对象的起点有多少字节。
一般来说,在C++中存在从指向派生类的指针到指向不论什么公有基类的提前定义转换。
在指向类成员的指针的情况下恰恰相反:存在从指向基类成员的指针到指向公有派生类成员的指针的隐式转换。但不存在从指向派生类成员的指针到指向其不论什么一个基类成员的指针的转换。
这个逆变性看起来有违直觉,只是,假设回顾指向数据成员的指针并不是一个对象的指针,而是对象内的一个偏移量,就会明确了。
class Shape{
Point center_;
};
class Circle : public{
double radius;
};
由于一个Circle也是一个Shape。所以一个Circle对象内包括一个Shape子对象。因而。Shape内的不论什么偏移量在Circle内也是一个有效的偏移量。
PointCircle::*loc = &Shape::center_; //ok,从基类到派生类的转换
然而,一个Shape未必是一个Circle,因此一个Circle的成员的偏移量在Shape内未必是一个有效的偏移量。
doubleShape::*extent = &Circle::radius_; //错误!
从派生类到基类的转换
16指向成员函数的指针并不是指针
和指向常规函数的指针不同,指向成员函数的指针能够指向一个常量成员函数。
和指向数据成员的指针的情形一样。为了对一个指向成员函数的桌子非常进行解引用,须要一个对象或一个指向对象的指针。
对于指向数据成员的指针的情形,为了訪问该成员,须要将对象的地址和成员的偏移量相加。对于指向成员函数的指针的情形,须要将地相的地址用作this指针的值,进行函数调用,以及其它用途。
和数据成员的指针一样,指向成员函数的指针也表现出一种逆变性。即存在从指向基类成员函数的指针到指向派生类成员函数指针的提前定义转换。反之则不然。
class B{
public:
void bset(int val){ bval_ = val;}
private:
int bval_;
};
class D : publicB{
public:
voiddset(int val){ dval _ = val;}
private:
int dval_;
};
B b;
D d;
void (B::*f1)(int)= &D::dset; //错误!不存在这样的反向逆转
(b.*f1)(12); //哎呀!訪问不存在的dval成员。
void (D::*f2)(int)= &B::bset; //OK,存在这样的转换
(d.*f2)(11); //OK。设置继承来的bval_数据成员
C++必知必会(1)