您的位置:首页 > 编程语言 > C语言/C++

C++拾遗

2012-02-29 22:38 113 查看
一些平常要用到的容易疏漏的地方。
1、类的静态函数不能访问非静态数据;

原因:因为静态成员函数不传递this指针, 不和具体实例关联, 所以不能访问非静态member, 多用于callback。

2、类的静态函数不能声明为const、volatile、virtual;

static没有this指针,而这些的实现要求有this指针。

3、类的const成员函数不能改变对象的普通数据成员,但可以改变static和mutable的数据成员。

4、const对象只能调用const成员函数。

5、const可以用作函数重载的依据之一,和参数类型、返回值类型一样。

6、类的成员函数在调用会插入一个ClassName *Const this指针作为参数;而类的const成员函数在调用的时候会插入const ClassName *Const this指针作为参数。

7、没有指向引用的指针,因为引用并没有开放给外部的地址空间(不像普通的变量)。切记,把引用看作是变量的别名。

8、const引用是指不能通过引用改变变量的值,而非变量的值不能被修改;const 引用可以初始化为不同类型的对象或者初始化为右值(非const引用不可以):

int i=5;

const int &j=i;

i=4;//合法

j=4;//非法!!

———————–

int x=3;

const int& r=x; //正确

int& const r=x; //未定义,可能被编译器忽略,因为引用本来就是const(初始化后不能重新绑定)

——————–

const int ival = 1024;//ival为const

int &ref2 = ival;//错误!把一个const int 引用为非const类型

—————–

int& a =719;//错误

const int& a = 719;//正确,const引用可以初始化为右值

9、常量指针与指针常量:

char const *p //指向const字符的指针,这个const字符的值不能改变

const char *p //同上,也是常量指针

char * const p //指针本身是const ,不能改变指针的指向,但可以改变字符的内容,是指针常量

10、下列四种情况必须使用类的初始化列表:

a、初始化引用(reference)成员;

b、初始化 const 成员;

c、调用基类的构造函数,并且该基类的构造函数有参数时;

d、调用一个类成员的构造函数,并且它有参数时。

同时暗示着,如果有上面的四种情况,那么必须有构造函数来初始化它们。

11、iostream,输入输出流,流(stream)这个单词的意思是:试图说明字符是随着时间顺序生成或消耗的。

12、与其他变量不同,除非特别说明(extern),在全局作用域声明的 const 变量是具有文件作用域的局部变量。不能被其他文件访问。通过指定
const 为 extern(也即 extern const int a = 4;)可以跨文件作用域。

13、枚举成员是常量表达式,并且枚举成员值可以是不唯一的:

enum CivilNet

{

gemfield = 2, //2

leaflower,//3

syszux = 3, //3

civilnet_cn//4

};

14、const char name[8] = “Gemfield”; // error: Gemfield is 9 elements

15、一个数组不能用另外一个数组初始化,也不能将一个数组赋值给另一个数组。

16、指针和 typedef

typedef string *pstring;

const pstring cstr;//string *const cstr而非const string *cstr

17、可使用跟在数组长度后面的一对空圆括号,对数组元素做值初始化:

int *gemfield = new int[10] (); //初始化为0

这种方法在const 对象的动态数组中就有用了:

const int *gemfield= new const int[100]();//必须初始化

不过这样的数组用处不大。

18、数组指针:

int *ip[4]; // 数组里面包含4个指针,指向int

int (*ip)[4]; // 指向一个包含有4个int的数组

注意[]的优先级要高。

19、后自增、自减运算符的优先级:

cout << *iter++ << endl;

由于后自增操作的优先级高于解引用操作,因此 *iter++ 等效于 *(iter++)。子表达式 iter++ 使 iter 加 1,然后返回
iter 原值的副本
作为该表达式的结果。因此,解引用操作 * 的操作数是 iter 未加 1 前的副本

20、零值指针的删除:

如果指针的值为 0,则在其上做 delete 操作是合法的,但这样做没有任何意义:

int *ip = 0;

delete ip; // ok

C++ 保证:删除 0 值的指针是安全的。

21、c++的强制类型转换:

dynamic_cast//无法正确转换那么会返回一个值是0的指针

const_cast //去掉const性质

static_cast//只使用于表达式的原类型与目标类型相适应的情况

reinterpret_cast //用于替代c中与实现相关的或不安全的强制转换

22、预处理器还定义了其余四种在调试时非常有用的常量:

__FILE__ 文件名

__LINE__ 当前行号

__TIME__ 文件被编译的时间

__DATE__ 文件被编译的日期

23、内联函数应该在头文件中定义,这一点不同于其他函数。

inline 函数可能要在程序中定义不止一次,只要 inline 函数的定义在某个源文件中只出现一次,而且在所有源文件中,其定义必须是完全相同的。把
inline 函数的定义放在头文件中,可以确保在调用函数时所使用的定义是相同的,并且保证在调用点该函数的定义对编译器可见。

24、出现在相同作用域中的两个函数,如果具有相同的名字而形参表不同,则称为重载函数。任何程序都仅有一个 main 函数的实例。main 函数不能重载。

如果两个函数声明的返回类型和形参表完全匹配,则将第二个函数声明视为第一个的重复声明。如果两个函数的形参表完全相同,但返回类型不同,则第二个声明是错误的。

函数不能仅仅基于不同的返回类型而实现重载。

仅当形参是引用或指针时,形参是否为 const 才能实现重载。

25、如果我们碰到复杂的类型声明,该如何解析它?例如:

char (*a[3])(int);

a到底被声明为什么东东?指针?数组?还是函数?分析时,从a 最接近(按运算符优先级)处开始。我们看到a最接近符号是[ ]——注意:*比[ ]的优先级低。a后既然有[ ],那么a是数组,而且是包含3个元素的数组。 那这个数组的每个元素是什么类型呢?a[3]是什么类型?是指针,因为它的前面有*. 由此可知,数组a的元素是指针。光说是指针还不够。对于指针,必须说出它指向的东东是什么类型。它指向的东东是什么,就看*a[3]是什么(a[3]是指针,它指向的东东当然是*a[3])了。继续按优先级观察,我们看到*a[3]后面有小括号,所以可以肯定*a[3]是函数。即数组a的元素是指向函数的指针。指向的是什么类型的函数?这很明显,是入参为int、返回值为char的类型的函数。

26、stringstream 对象的一个常见用法是,需要在多种数据类型之间实现自动格式化时使用该类类型。

int val1 = 512, val2 = 1024;

ostringstream format_message;

format_message << “val1: “<< val1<< “val2: ” << val2;//int->string

istringstream input_istring(format_message.str());

string dump;

input_istring >> dump >> val1 >> dump >> val2;//string->int

27、除了引用类型外,所有内置或复合类型都可用做元素类型。引用不支持一般意义的赋值运算,因此没有元素是引用类型的容器。C++ 语言中,大多数类型都可用作容器的元素类型。容器元素类型必须满足以下两个约束:

元素类型必须支持赋值运算;

元素类型的对象必须可以复制。

28、类的static成员在整个类的派生体系中只有一个实例(也即:如果基类定义 static 成员,则整个继承层次中只有一个这样的成员。无论从基类派生出多少个派生类,每个 static 成员只有一个实例),并且类的static成员在类外初始化,而类的const static成员在类中定义时即要初始化;一定注意类的静态成员与类的对象是完全“异步”的生命期;

29、对于有虚函数的基类来说,其析构函数也应当被声明为virtual;

30、重载赋值操作符时,应该返回*this的引用,这是一项传统;

31、当需要在返回引用和返回对象间做决定时,你的职责是选择可以完成正确功能的那个。至于怎么让这个选择所产生的代价尽可能的小,那是编译器的生产商去想的事;

32、在类内部定义的成员函数,将自动作为 inline 处理;

33、在创建类的对象之前,必须完整地定义该类。注意是必须定义类,而不只是声明类;

34、当类B的成员为一个类A类型时,只有当类A定义已经在前面出现过,B的数据成员才能被指定为A类型。如果A是不完全类型,那么B的数据成员只能是指向A类型的指针或引用;

35、不能从 const 成员函数返回指向类对象的普通引用。const 成员函数只能返回 *this 作为一个 const 引用;

36、有时(但不是很经常),我们希望类的数据成员(甚至在 const 成员函数内)可以修改。这可以通过将它们声明为 mutable 来实现。可变数据成员(mutable data member)永远都不能为 const,甚至当它是 const 对象的成员时也如此;

37、可以通过将构造函数声明为 explicit,来防止在需要隐式转换的上下文中使用构造函数;explicit 关键字只能用于类内部的构造函数声明上。在类的定义体外部所做的定义上不再重复它;

38、友元声明可以出现在类中的任何地方:友元不是授予友元关系的那个类的成员,所以它们不受声明出现部分的访问控制影响;

39、有些类需要完全禁止复制。例如,iostream 类就不允许复制。如果想要禁止复制,似乎可以省略复制构造函数,然而,如果不定义复制构造函数,编译器将合成一个。为了防止复制,类必须显式声明其复制构造函数为 private;

40、不能重载的操作符如下(并且操作符重载不改变优先级)

::

.*

.

?:


41、使用 private 或 protected 派生的类不继承基类的接口,相反,这些派生通常被称为实现继承。派生类在实现中使用被继承但继承基类的部分并未成为其接口的一部分。因为:

1、如果是公用继承,基类成员保持自己的访问级别:基类的 public 成员为派生类的 public 成员,基类的 protected 成员为派生类的 protected 成员。

2、如果是受保护继承,基类的 public 和 protected 成员在派生类中为 protected 成员。

3、如果是私有继承,基类的的所有成员在派生类中为 private 成员。

42、继承与组合

1、定义一个类作为另一个类的公用派生类时,派生类应反映与基类的“是一种(Is A)”关系。

2、类型之间另一种常见的关系是称为“有一个(Has A)”的关系。

3、public继承是一种 is-A的关系,而private是has -A的关系,而protected是一种“is-implemented-in-terms-of A”的关系。

43、友元关系不能继承。基类的友元对派生类的成员没有特殊访问权限;

44、如果是 public 继承,则用户代码和后代类都可以使用派生类到基类的转换。如果类是使用 private 或 protected 继承派生的,则用户代码不能将派生类型对象转换为基类对象。如果是 private 继承,则从 private 继承类派生的类不能转换为基类。如果是 protected 继承,则后续派生类的成员可以转换为基类类型;

45、一个类只能初始化自己的直接基类。如果类 C 从类 B 派生,类 B 从类 A 派生,则 B 是 C 的直接基类。虽然每个 C 类对象包含一个 A 类部分,但 C 的构造函数不能直接初始化 A 部分。相反,需要类 C 初始化类 B,而类 B 的构造函数再初始化类 A。这一限制的原因是,类 B 的作者已经指定了怎样构造和初始化 B 类型的对象。像类 B 的任何用户一样,类 C 的作者无权改变这个规约;

46、赋值操作符通常与复制构造函数类似:如果派生类定义了自己的赋值操作符,则该操作符必须对基类部分进行显式赋值。赋值操作符必须防止自身赋值;

47、与基类成员同名的派生类成员将屏蔽对基类成员的直接访问,即使函数原型(比如参数)不同,基类成员也会被屏蔽。可以使用作用域操作符访问被屏蔽的基类成员;

48、如果派生类重定义了重载成员,则通过派生类型只能访问派生类中重定义的那些成员。如果派生类想通过自身类型使用所有的重载版本,则派生类必须要么重定义所有重载版本,要么一个也不重定义;

49、派生类可以恢复继承成员的访问级别,但不能使访问级别比基类中原来指定的更严格或更宽松。

class Base {

public:

std::size_t size() const { return n; }

protected:

std::size_t n;

};

class Derived : private Base { . . . };

class Derived : private Base {

public:

// 将私有的访问级别恢复至public级别

using Base::size;

protected:

using Base::n;

};

一个 using 声明只能指定一个名字,不能指定形参表,因此,为基类成员函数名称而作的 using 声明将该函数的所有重载实例加到派生类的作用域。将所有名字加入作用域之后,派生类只需要重定义本类型确实必须定义的那些函数,对其他版本可以使用继承的定义。

50、含有(或继承)一个或多个纯虚函数的类是抽象基类。除了作为抽象基类的派生类的对象的组成部分,不能创建抽象类型的对象(不能实例化抽象基类abstract base class);

51、多重继承中,基类构造函数按照基类构造函数在类派生列表中的出现次序调用,总是按构造函数运行的逆序调用析构函数;

52、CivilNet社区推荐的学习c++的书籍:

《c++ primer》

《effective c++》

《Inside c++ object model》

《Design patterns》

《introduction to algorithms》

《Advanced UNIX Programming》

《Understanding the Linux Kernel》

《Qt source code》

《STL源码剖析》

《微机原理》

《数字电路》

《模拟电路》

更多内容,敬请期待……
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: