分类: C/C++
2009-08-04 18:08:14
《C++编程思想》阅读笔记
作者:八点半 时间:2009/7–8 Email:zuoan911@gmail.com
1、声明和定义的区别:声明是向编译器介绍标示符,extern表示变量声明而非定义;定义为标示符分配存储空间(内存)。
2、带空参数表的函数声明,C和C++有很大不同:在C语言中,声明int func(); 表示“一个可带任意参数(任意数目,任意类型)的函数”,这将妨碍类型检查;而在C++中表示“一个不带参数的函数”
3、就名字空间包含来讲:
#include
#include
using namespace std; //std中封装了标准C++库
4、C预处理器的一个重要功能是可以进行字符数组的拼接,例如:
cout<<"this is far too long to put on a"
"single line but it can be broken up with"
"no ill effects\n as long as there is no"
"array.\n";
5、for循环语句的执行顺序:
for(statement1; statement2; statement3)
statement4;
顺序:statement1->statement2->statement4->statement3->statement2
6、switch:选择器类型可为整型或字符型,bool型亦可。不可为实类型,float,double等
void foo(char* a, char* b, int len) {
switch (len & 0x7) {
default:
while (len > 7) {
len -= 8; *a++ = *b++;
case 7: *a++ = *b++;
case 6: *a++ = *b++;
case 5: *a++ = *b++;
case 4: *a++ = *b++;
case 3: *a++ = *b++;
case 2: *a++ = *b++;
case 1: *a++ = *b++;
}
}
}
switch不管有无括号,都会首先匹配case语句,此题匹配完后,case语句都位于while循环中,故会接着执行下去,知道退出while循环。来源:http://rednaxelafx.javaeye.com/blog/132053
7、头文件是存放接口规范的地方,编写头文件的基本原则是“只限于声明”,即只限于对编译器的信息,不涉及通过生成代码或创建变量而分配存储空间的信息。当然,在头文件中定义一个静态变量,也是正确的。
另外,不要在头文件中放置使用指令(using namespace std)
8、面向对象编程可总结为一句话:向对象发送消息。而技巧是设计对象和消息。
9、句柄类:a 隐藏实现;b 减少重复编译。实现方法:将类的private部分放在一个结构中,结构不在头文件中定义。
句柄类实现模板:
#ifndef HANDLE_H
#define HANDLE_H
class Handle{
private:
struct Cheshire; //只声明
Cheshire *smile; //使用指针访问,指针大小固定
public:
Handle();
set();
get();
......
~Handle();
};
#endif
#include “handle.h”
struct Handle::Cheshire //该结构存放所有Handle的private成员
{
int val;
};
Handle()
{
simle = new Cheshire; //定义结构,分配内存
simle->val = 0;
}
......
~Handl()
{
delete simle; //释放
}
10、函数重载:参数列表不同,函数名相同的多个函数。
函数重载的实现:函数的内部名附带上参数类型,如
void print(char c);
void print(float f); 编译成:_print_char和_print_float的内部名
11、在C++中struct和class唯一的不同之处就在于,struct默认为public,class默认为private。
12、默认参数的规则:
a、只有参数列表的后部参数才可以是默认的,不可以在一个默认参数后面又跟一个非默认的参数;
b、一旦在一个函数调用中开始使用默认参数,那么这个参数后面的所有参数都必须为默认的,这可
从第一条中推出;
c、默认参数只能放在函数声明中,通常在一个头文件中。
13、标准C具有字符串化运算符:#。用在预处理器宏定义中,可获得任何一个表达式并把它转换成一个字符数组。如:#define P(EX) printf("%s : %d\n", #EX, EX)
14、C++中const默认为内部连接,也就是说,const仅在const被定义过的文件里才是可见的,而在连接时不能被其他编译单元看到。当定义一个const时,必须赋一个值给它,除非用extern作出了清楚地说明:extern const int bufsize; 通常C++编译器并不为const创建存储空间,它把这个定义保存在他的符号表里,但上面的extern强制进行了存储空间的分配(P176),另外还有其他一些情况,如取一个const的地址,或是一个编译期间不知道初始值的const,或为const集合,也要进行存储空间分配。例如:
const int i = 100;
const int j = i + 10;
long addr = (long)&j; //需获取const地址,需分配存储空间
char buf[j+10];
const char c = cin.get(); //编译期间初始值不知道,需分配存储空间
const char c2 = c + ‘a’;
15、若为const集合,则编译期间不能使用它的值,如:
const int i[] = {1, 2, 3, 4};
float f[i[2]]; //错误,为const集合,其分配的存储空间内的内容,编译期间并不需要知道,P177
16、C和C++中const的区别(P178):
a、C中const默认为外部链接,C++中默认为内部连接;
b、C中一个const总是需要创建一块内存空间,C++中根据使用情况而定,若仅用于用一个名字替代一个值(如#define用法),则不 分配存储空间。
17、指针常量: Type * const pointer ; //必须把const表明放在*右边才是一个const指针
常量指针: const Type *pointer ; 或Type const* pointer
不存在const* char它无法通过编译
从右向左读(* 读成 pointer to)
char * const p; //读作p is a const pointer to char
const char * p; //读作 p is a pointer to const char
char const * p; //与 const char * p;读法一样)
18、char* str = "hello"; hello被编译器作为一个常量字符数组建立,str是指向常量字符数组首地址的指针,但是,修改该字符数组的任何字符都会导致运行时错误(并不是所有的编译器都会做到这一点),好的风格是这么定义:char str[] = "hello";
19、有一种观点:C中的一切都是按值来传递的,传递指针时,也会得到一份副本,即也是通过值传递指针的。
20、临时量:临时量自动生成为常量,改变临时量是错误的,因为编译器使所有临时量自动生成为const的。临时量常产生于函数返回值等。
21、一个内部类型的static const可以看作一个编译期间的常量,同时,必须在static const定义的地方初始化,而类中所有其他数据成员必须在构造函数或其他成员函数里初始化。如:
class Example
{
private:
static const int size = 100; //必须在定义时初始化
const string * stack[size];
.............
}
22、const对象不能调用非const成员函数;const成员函数调用const、非const对象和非const数据成员都是安全的;非const对象调用const和非const成员函数是正确的。
23、const实现有按位const和按逻辑const:
a按位const意思是对象中的每个字节都是固定的,对象的每个位映象都从不改变;
b按逻辑const意思是整个对象从概念上讲是不变的,但是可以以成员为单位改变。
编译器默认是将const对象编译成按位const,保证其常量性。
24、实现按逻辑const的方法:
1、关键字mutable,指定一个特定的数据成员可以在一个const对象里被改变,如:
class Z
{
private:
int j;
mutable int i;
public:
void f() const
{
j++; //错误
i++; //正确,有mutable标识
}
}
2、此功能实现的另一种方法是将this指针强制转换成当前对象类型的指针,再进行操作:
((Z*)this)->i++; 或 (const_cast
25、内联函数:必须使函数体和声明结合在一起才有效,即inline关键字必须和函数定义在一起。
任何在类内部定义的函数自动成为内联函数。
26、一般任何类型的循环都会被认为太复杂而不扩展为内联函数。
27、向前引用:
class Forwar
{
private:
int i;
public:
int f() const { return g()+1; } /*在此之前g()未定义,但C++语言规定:只有在类声明结束后,其中的内联函数才被计算因此这是可以的。*/
int g() const { return i; }
};
28、标志粘贴:##。它允许设两个标识符,并把他们粘贴在一起自动产生一个新的标示符。例如:
#define FILED(s) char* s##_string; int s##_size
29、在C和C++中,static都有两种基本的定义:
a 在固定的地址上进行存储分配,也就是说对象是在一个特殊的静态数据区上创建的,而不是每次函数调用时在堆栈上产生的,也就是静态存储的概念。
b 对一个特定的编译单位来说是局部的,这样static控制名字的可见性,这个名字在本单元或类之外是不可见的。这也描述了连接的概念,它决定连接器将看到哪些名字。
30、所有的全局对象都默认是静态存储的,这些对象在程序进入main()之前,就已经被初始化了。
31、名字空间:
a、namespace只能在全局范围内定义,但他们之间可以相互嵌套;
b、在namespace定义的结尾,右花括号的后面不必跟一个分号;
c、一个namespace可以再多个头文件中用一个标示符来定义,就好像重复定义一个类一样,但这里的意思是对同一名字空间追加名字。
d、一个namespace可以用另一个名字来作它的别名:
namespace BobsSuperDuperLibrary{
class widget{ .....}
............
}
namespace Bob = BobsSuperDuperLibrary;
e、不能像类那样去创建一个名字空间的实例。
32、使用名字空间的三种方法:作用域解析(::);使用指令(using namespace xxx);使用声明(using namespace xxx)
33、类中的静态成员:
a、静态数据成员定义必须出现在类的外部,而且只能定义一次;静态成员函数则可以在类内部定义。类中静态数组初始化方法:
int CLASS::array[] = {x, y, z,.....};
b、嵌套类(在另一个类内部定义)中可以有静态数据成员,而局部类(在函数内部定义的类)中则不能有。
c、类中的静态成员函数只能访问静态数据成员,也只能调用其他的静态成员函数,因为静态成员函数没有this指针。
34、引用(&)就像能自动地被编译器间接引用的常量型指针。应用要点是任何引用必须和存储单元联系,访问引用时,就是在访问那个存储单元。它是该存储单元的别名,它不新创建副本,因此和被引用的对象共用内存。
const int& q = 12; //正确 int x; int& a = x; //正确
35、使用引用的原则:
a、引用必须在创建时初始化;
b、一旦一个引用被初始化为指向一个对象,它就不能改变为另一个对象的引用;
c、不可能有NULL引用,必须确保引用时和一块合法的存储单元关联的。
36、常量引用:void f(int& i); f(1); //错误,1是常量,应改成:void f(const int& i);
指针引用:void f(int*& p);
37、拷贝构造函数X(X&):(P251),解决对象按值传递时编译器对其进行按位拷贝的问题(C/C++参数传递默认都是按位拷贝实现的)。如:
class X{}
X func(X x)
{
.....
}
在调用函数func时(func(x)),若无拷贝构造函数,编译器在拷贝x对象作为func的形参时,以按位拷贝的方式,此时,构造函数不会被调用,但析构函数会调用。若有拷贝构造函数,则会调用拷贝构造函数。
38、仅当准备用按值传递的方式传递类对象时,才需要拷贝构造函数,否则,不需要。类对象抑制按值传递的方法:声明一个私有的拷贝构造函数,甚至不必去定义它。这样如果用户试图用按值传递的方式传递或返回对象,编译器就会报错,因为拷贝构造函数是私有的,除非类的成员函数或友元函数以按值传递方式传递对象。 X(const X& x); //拷贝构造的函数头模型,注意参数需为引用
39、运算符重载:当运算符被重载为全局函数时:对于一元运算符是一个参数,二元是两个参数;当运算符被重载为成员函数时:一元运算符没有参数,二元运算符有一个参数,运算符左侧的那个对象为调用重载函数的实例对象。
40、++和--重载时,参数列表中含有int是后递增(减),无int型参数则是前递增(减),参数int是用来表示后缀的哑元常量。
41、operator=,operator[]只允许作为成员函数,若为全局的,那么会重定义内置的=,[]等运算符。重载的总原则:检查自赋值,尤其是operator=。
42、运算符重载中的参数和返回值:
a、对于任何参数,如不会改变它,默认作为const引用传递,否则作为引用传递;
b、返回值多为引用且为常量引用,赋值运算符的返回值应该是非常量引用。
43、返回值优化:return Integer(left.i + right.i); 若用 Integer tmp(left.i + right.i);,会发生三件事:a、创建tmp对象,包括构造函数的调用;b、拷贝构造函数把tmp拷贝到外部返回值存储单元里;c、tmp作用域结束时调用析构函数。而用返回值优化,编译器则直接将对象创建在外部返回值存储单元,只调用一次构造函数。
44、不能重载的运算符:成员选择operator.、成员指针间接引用operator.*、没有求幂运算符**;不存在用户自定义运算符;不能改变运算符优先级。
45、MyType b;//调用构造函数
MyType a =b; //调用拷贝构造函数,未定义的对象,必须调用构造函数初始化
a = b;//调用a.operator=(b)
46、当类中含有指针时,必须定义的4个函数:普通构造函数,拷贝构造函数,operator=运算符重载,析构函数。
47、如果类包含对象或是从别的类继承来的,operator=会被递归调用。
48、实现自动类型转换的方式:
a、构造函数转换:如果一个类的构造函数以另一个类型的对象(或引用)为单一参数,那么这个构造函数允许编译器执行自动类型转换。使用关键字explicit能抑制上述现象:explicit myClass2(myclass c&)。构造函数技术是目的类执行转换。
2、运算符转换:创建一个成员函数:operator DesType (),不用指定返回值--返回类型就是正在重载的运算符名字。运算符转换是源类进行转换。