读书笔记之:C/C++代码精髓
第1章 更好的C
1.简介
C++是一个混合语言,它将面向对象特征与系统编程语言混合在一起。
C语言的兼容性是C++设计的一个主要的目标,1989年Bjarne在ANSI C++委员会的主题演讲中提到,C++是“工程上的妥协”,并且必须使它“越接近C越好,但不能过度”。
C++是多范例语言:像C和Pascal一样支持过程编程,像Ada支持数据抽象和通用性(模板),像其他面向对象一样,支持继承和多态。
2. 更好的C
C++比C更安全,更富于表达,所以可以将它作为更好的C使用:类型安全链接,强制的函数原型,内嵌函数,const限定修饰符,函数重载,缺省参数,引用,动态内存管理。
C++中用户自定义类型也允许隐式转换,然而在任一个转换序列中只允许有一个隐式用户定义的转换。
可以通过声明单参数构造函数explicit来防止隐式转换。
在C++中,explicit关键字用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换。
explicit使用注意事项:
* explicit 关键字只能用于类内部的构造函数声明上。
* explicit 关键字作用于单个参数的构造函数。
下面是一个比较好的例子:
class Circle { public: Circle(double r) : R(r) {} Circle(int x, int y = 0) : X(x), Y(y) {} Circle(const Circle& c) : R(c.R), X(c.X), Y(c.Y) {} private: double R; int X; int Y; }; int _tmain(int argc, _TCHAR* argv[]) { //发生隐式类型转换 //编译器会将它变成如下代码 //tmp = Circle(1.23) //Circle A(tmp); //tmp.~Circle(); Circle A = 1.23; //注意是int型的,调用的是Circle(int x, int y = 0) //它虽然有2个参数,但后一个有默认值,任然能发生隐式转换 Circle B = 123; //这个算隐式调用了拷贝构造函数 Circle C = A; return 0; } |
加了explicit关键字后,可防止以上隐式类型转换发生 class Circle { public: explicit Circle(double r) : R(r) {} explicit Circle(int x, int y = 0) : X(x), Y(y) {} explicit Circle(const Circle& c) : R(c.R), X(c.X), Y(c.Y) {} private: double R; int X; int Y; };
|
3. C++中的IO
一个从流中读取的函数称为提取器,而一个输出函数称为插入器。如
main()
{
char c;
while(cin.get(c))
cout.put(c);
}
其中,get提取器从流中把下一个字节存放在char引用参数中。
getline也是提取器,>>就是一个插入器。
流输入输出中的格式化标志位是位掩码值,可以使用setf来设置,使用unsetf来复位。
使用presion函数来指定显示的小数的位数。
如:
cout.presion(3);这样就设置了在输出中使用3位小数。
cout.setf(ios::oct,ios::basefield);设置以8进制输出。
C++中的endl是一个操纵器,还包括其他许多操纵器,可以实现与setf类似的功能。
C++中的运算符重载,主要是包括全局函数和成员函数两种
C++对于C 的兼容性
(1). 在C++中全局const声明默认的是内部链接,而在C 中却是外部链接,所以对于C++,可以在一个文件中使用const定义来取代#define宏定义,如果希望一个const对象具有外部链接特性,就必须使用extern关键字。
(2).在c中可以将任意类型的指针指向空类型void*,或将指针从空类型void*指向任何其他类型,而在C++中不允许在没有转换类型的情况下将一个空类型指针指向其他类型。
第2章 指针
C++中指针与数组
向整数中压缩数据:date=(year<<9)|(month<<5)|day;
struct 位域结构:
struct Date
{
unsigned day:5;
unsigned month:4;
unsigned year:7;
}
如果要使用上述结构来表示一个整型数的话,只需将指向整型的指针强制转化为指向Date的指针。
(1)一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。(2)由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进位。(3)位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。
第3章 预处理
1. C编译器被允许为标准C库中的任何函数提供宏形式(如getc和putc为了效率经常被当作宏来用)。库函数中的宏形式在实际编译时隐藏其原型,所以其参数在翻译时不进行类型检测。为了加强调用真正的函数,用#undef指令去掉宏定义。如:
#undef getc
或者当调用时可以把函数名用括号括起来。
C=(getc)(stdin);
2. 预处理运算符
defined运算符
#if defined(X) 等价于 #ifdef X
#if !defined(X) 等价于 #ifndef X
但defined运算符更加灵活。
#运算符是字符串化,将其后面的解释为字符串
## 加标记:将两个标记连结在一起形成单个标记
3. assert宏的实现
#define assert(cond) ((cond)?(void)0:_assert(#cond,_FILE_,_LINE_))
void _assert(char*cond,char*filename,long lineno)
{
}
第4章 C标准库
第8章 模板
1. 模板特化
类模板特化,
using std::cout;
template<typename T>
class A
{
public:
A(){cout<<"primayr\n";}
};
template<>
class A<char>
{
public:
A(){cout<<"char specialization\n";}
};
template<>
class A<float>
{
public:
A(){cout<<"float specialization\n";}
};
int main()
{
A<int> a1;
A<char> a2;
A<float> a3;
}
函数模板特化,
#include <string>
#include <cstring>
using std::cout;
using std::endl;
using std::string;
template<class T>
size_t bytes(T& t)
{
cout<<"using primary templates\n";
return sizeof t;
}
size_t bytes(char*& s)
{
cout<<"using char* overload\n";
return strlen(s)+1;
}
template<>
size_t bytes<string>(string&s)
{
cout<<"using string explicit specialization\n";
return sizeof s;
}
template<>
size_t bytes<float>(float &x)
{
cout<<"using float explicit specilization\n";
return sizeof x;
}
int main()
{
int i;
cout<<"bytes in i:"<<bytes(i)<<endl;
char *s="hello";
cout<<"bytes in s:"<<bytes(s)<<endl;
string t("very good");
cout<<"bytes in t:"<<bytes(t)<<endl;
float y;
cout<<"bytes in y:"<<bytes(y)<<endl;
return 0;
}
部分类模板特化
using std::cout;
template<class T,class U>
class A
{
public:
A(){cout<<"primary template\n";}
};
template<class T,class U>
class A<T*,U>
{
public:
A(){cout<<"<T*,U> partial specialization\n";}
};
template<class T>
class A<T,T>
{
public:
A(){cout<<"<T,T> partial specialization\n";}
};
template<class U>
class A<int,U>
{
public:
A(){cout<<"<int,U> partial specialization\n";}
};
int main()
{
A<char,int >a1;
A<int*,float>a2;
A<double,double>a3;
A<int,char>a4;
return 0;
}
第9章 位操作
1.用于无符号整整的位函数
#define SET(n,i) ((n)|(1u<<i)) //置1
#define RESET(n,i) ((n)&~(1u<<i)) //复位
#define TOGGLE(n,i) ((n)^(1u<<i)) //取反
#define TEST(n,i) !!((n)&(1u<<i)) //测试
2. vector<bool>模板特化
该容器经过优化后用来存储位,它从左到右的定向,被认为是一个动态分配字节长度的位字符串
bitset类模板支持有效的固定大小的位设置操作
vector<bool>模板规范支持动态大小的位字符串
第14章 面向对象编程
1. 虚函数与纯虚函数
vptr,vtbl
其实,编译器在编译时并不知道要调用的函数体的正确位置,但它插入了一段能找到正确的函数体的代码。这称之为晚捆绑(late binding)或运行时捆绑(runtime binding) 技术。
通过virtual 关键字创建虚函数能引发晚捆绑,编译器在幕后完成了实现晚捆绑的必要机制。它对每个包含虚函数的类创建一个表(称为VTABLE),用于放置虚函数的地 址。
在每个包含虚函数的类中,编译器秘密地放置了一个称之为vpointer(缩写为VPTR)的指针,指向这个对象的VTABLE。所以无论这个对象包 含一个或是多少虚函数,编译器都只放置一个VPTR即可。
VPTR由编译器在构造函数中秘密地插入的代码来完成初始化,指向相应的VTABLE,这样对象 就“知道”自己是什么类型了。 VPTR都在对象的相同位置,常常是对象的开头。这样,编译器可以容易地找到对象的VTABLE并获取函数体的地址。
如果我们用sizeof查看前面Base类的长度,我们就会发现,它的长度不仅仅是一个int的长度,而是增加了刚好是一个void指针的长度(在我的机器里面,一个int占4个字节,一个void指针占4个字节,这样正好类Base的长度为8个字节)。
每当创建一个包含虚函数的类或从包含虚函数的类派生一个类时,编译器就为这个类创建一个唯一的VTABLE。在VTABLE中,放置了这个类中或是它的 基类中所有虚函数的地址,这些虚函数的顺序都是一样的,所以通过偏移量可以容易地找到所需的函数体的地址。假如在派生类中没有对在基类中的某个虚函数进行 重写(overriding),那末还使用基类的这个虚函数的地址(正如上面的程序结果所示)。
第15章 算法
1. 函数对象
函数对象作为C++重要的技术之一存在如下优势:
- 函数对象有自己的状态,这点一般函数无法相比,对于函数对象可以同时存在两个完全不同状态的实体。
- 函数对象有自己的型别。尽管函数也有自己的型别,但是它的型别却是有限制的指针,所以在有些地方函数对象是不能取代的。
- 函数对象可能还有更快的执行速度,因为它可以在无法所有的情形进行展开,函数却有许多限制。
在继续说明函数对象之前,我们先看看一个最简单的函数对象的示例,在现实中它可以没有多少优势,但它可以把什么是函数对象表现的很具体与显明:
struct functionor
{
operator()()
{
std::cout << "这是一个简单的函数对象" << std::endl;
}
};
int main()
{
functionor fun;
fun(); //这就像一个函数一样被调用,但是fun却一个对象
return 0;
}
第16章 标准容器
第17章 文本处理
1. 赋值抑制
%n用法
第18章 文件处理
第20章 动态内存
1.在标准C中使用堆
malloc,calloc,realloc
new和delete运算符
附录:
1.C++中main()函数不能够被递归的调用。
C语言中是允许main函数递归调用的,C++标准不允许,但是很多编译器包括g++、VC编译器都是支持的。