第一篇 C++ 概述
程序设计方法――算法集合与数据集合之间的关系。
在基于对象的程序设计方法中,通过数据抽象来建立问题模型,在 C++ 中称为类。
面向对象的程序设计方法通过继承机制和动态绑定机制扩展了抽象数据类型;继承机制是对现有实现代码的重用,动态绑定是指对现有的公有接口的重用。
C++ ―― 支持多种程序设计方法:面向对象的,基于对象的,基于过程的。
1 开始
1.1 问题的解决
1.2 C ++ 程序
标准 C++ 没有指定头文件的一个原因:头文件的后缀在 C++ 的不同实现中不相同。
1.3 预处理器指示符
预处理器指示符用“#”号标识。
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
// 编译 C++ 程序,编译器自动包含这个预处理名字: __cplusplus
// 如果是编译标准 C ,会包含: __STDC__
#ifdef __cplusplus
cout << "cpluscplus" << endl;
#endif
cout << __FILE__ << " " << __LINE__ << endl; // 当前文件,当前行
// 当前被编译文件的编译时间与日期
cout << __TIME__ << " " << __DATE__ << endl;
return 0;
}
1.4 注释
通常,把注释放在要描述的文本之上比较合适。与其他软件文档一样,考虑到有效性问题,注释必须随着软件的发展而升级。
1.5 输入 / 输出起步
2 C ++ 浏览
2.1 内置数组数据类型
虽然 C++ 对数组类型提供了内置支持,但是这种支持仅限于“用来读写单个元素”的机制。 C++ 不支持数组的抽象,也不支持对整个数组的操作。
数组是从 C 语言继承来的,它反映了数据与对其进行操作的算法的分离,而这正式过程化程序设计的特征。
2.2 动态内存分配和指针
在 C++ 中,指针的主要用途是管理和操纵动态分配的内存。
2.3 基于对象的设计
一般来说,函数调用比直接访问内存的开销要大得多,幸运的是, C++ 提供了解决方案:内联函数。
2.4 面向对象的设计
非公有成员到底该声明为 protected 还是 privated 类成员是新的设计准则。如果希望防止派生类直接访问某个成员,我们就把该成员声明为基类的 private 成员。如果确信某个成员提供了派生类需要直接访问的操作或数据存储,而且通过这个成员,派生类的实现会更有效,则我们把该成员声明为 protected 。
在高纯度的封装与实际效率之间, protected 访问级别提供了一个良好的折衷方案。
2.5 泛型设计
2.6 基于异常的设计
异常处理机制为统一的处理程序异常提供了语言一级的设施。
2.7 用其他名字来命名数组
为了使用名字空间中声明的名字,建议使用带有精细选择功能的 using 声明代替 using 指示符。
2.8 标准数组――向量
第二篇 基本语言
3 C ++ 数据类型
3.1 文字常量
当一个数值,例如 1 ,出现在程序中时,它被称为文字常量,不可寻址的。
文字常量可以添加后缀: l , L,f , F 等表示一定的数据类型。
字符文字前面可以加 L, 表示宽字符文件,类型: wchar_t 。用来支持某些语言的字符集合,如汉语。
字符串文字又称常量字符数组,它由字符串文字本身以及编译器加上的表示结束的空( null )字符构成。
浮点数的预设类型是 double 。
3.2 变量
也可以把变量说成对象。
变量和文字常量都有存储区,并且有相关的类型,区别在于变量是可寻址的。
区分左值和右值:左~内存的地址、右~存储的值,例如:
ch = ch – ‘ 0’ ; 左值 ―― 右值
如果一个变量是在全局域内定义的,那么系统会保证给它提供初始值 0 。
类机制通过所谓的缺省构造函数提供了类对象的自动初始化。例如:
int main()
{
int ival; // 未初始化的局部对象
string project; // 通过 string 的缺省构造函数进行初始化
return 0;
}
extern string name;// 声明
string name; // 定义
指针的类型可以指示编译器怎么样解释特定地址上内存的内容,以及该内存区域应该跨越多少内存单元。
C++ 提供了一种特殊的指针支持地址比较的需求,即空( void* )类型指针,它可以被任何数据指针类型的地址值赋值(函数指针除外)。
由于指针扮演如此突显的角色,执行时期的任何指针检验操作都会导致不可接受的效率成本,编译时期的指针检验工作很困难,因为往往无法决定这些指针在执行时期会有什么值。
3.4 字符串类型
string 类型能够自动将 C 风格字符串转换成 string 对象,但是反之需要使用函数: c_str() 。
例如:
string s1 (“guohonghua”);
char *str = s1.c_str(); // 错误!因为 c_str() 函数返回了一个指向常量数组的指针!
更改为:
const char *str = s1.c_str();
3.5 const 限定修饰符
const 对象的地址只能赋值给指向 const 对象的指针,但是,指向 const 对象的指针可以被赋以一个非 const 对象的地址。
const int ic; int *const cpi; const int *const cpic;
上述三个都是不合法的定义,因为常量对象必须有初值,任何人试图修改常量对象,会引发错误信息。
3.6 引用类型
3.7 布尔类型
在必要时,布尔类型会自动提升成算术类型。
3.8 枚举类型
我们不能打印枚举成员的实际变量名。
我们不能用枚举成员进行迭代。
在必要时,枚举类型会自动提升成算术类型。
class EnumTrying
{
public:
// 定义一个枚举类型,因为构造函数使用,所以必须在构造函数之前
enum _mode { input = 1024, output, bothput };
EnumTrying( _mode md );
};
EnumTrying::EnumTrying(_mode md )
{
cout << md << endl;
}
int main(int argc, char* argv[])
{
EnumTrying myEnumTrying(EnumTrying::input); // 必须包含名字空间
return 0;
}
3.9 数组类型
非 const 的变量不能 被用来指定数组的维数。
ia[1,2] ,在 C++ 中是合法的结构,但可能与程序员的意思相反,“ 1 , 2 ” 是一个逗号表达式,为单值 2 。 // ia[1][2]
3.10 vector 容器类型
vector 容器类型,为内置数组提供了一个基于对象的替代品。
使用 vector 有两种不同的形式,即所谓的数组习惯和 STL 习惯 。
vector 的初始化缺省值:算术和指针类型―― 0 , class ――缺省构造函数获得。
下标索引只能索引 vector 中已经存在的元素。
3.11 复数类型
包含的头文件为: <complex> 。
3.12 typedef 名字
typedef char *cstring;
extern const cstring cstr;
错误答案:指向 const 字符的指针。
正确答案:指向字符的 const 指针。等价于: char *const cstr;
3.13 volatile 限定修饰符
主要目的:提示编译器,该对象的值可能在编译器未检测到的情况下被改变。因此编译器不能武断地对引用这些对象的代码作优化处理。
3.14 pair 类型
<utility>: 在单个对象内把相同类型或不同类型的两个值关联起来。
例如: pair< string, string > author ( “James”, “Joyce” ) ;
可以用成员访问符合 first,second 访问单个元素。
3.15 类类型
4 表达式
4.1 什么是表达式?
一般的,表达式的结果是操作数的右值。
char *ptr;
ptr != 0 && *ptr != 0; // 判断是否指向一个对象,以及所指对象是否为非零值
4.2 算术操作符
两个整数相除的结果是整数。
% 操作符计算两个数相除的余数,但是如果一个或两个操作数为负,余数符号取决于机器,所以无法保证移植性。
4.3 等于、关系和逻辑运算符
if ( ival++ < ival ) // C/C++ 语言本身并没有定义计算顺序
cout << "right" << endl; // 无输出
if ( ival < ival+1 ) // 安全可移植的写法
cout << "right" << endl; // 有输出
说明:小于或等于操作符的操作数的计算顺序在标准 C 或 C++ 中都是未定义的,因此计算过程必须是顺序无关的!
凡表达式中含有 && 和 || 操作符时,其计算方式必定是由左至右。
二元操作符的计算次序在 C++ Standard 中并没有明确定义下来,编译器可自由提供最佳的实现方式。是“效率”与“易犯错误”之间的一种无可奈何的利益交换。
4.4 赋值操作符
指针类型和非指针类型不能混合赋值,例如,以下是非法的:
int ival, *pval;
//pval = ival = 0; // 不合法!。
4.5 递增和递减操作数
最一般的用法是对索引、迭代器或指向一个集合内部的指针加 1 或减 1 。
4.6 复数操作
#include <complex>
标准库提供的复数类是基于对象的类抽象的完美模型。通过使用操作符重载,我们几乎可以像使用简单内置类型一样简单。
但是,算术数据类型不能直接 被一个复数类对象初始化或赋值。
4.7 条件操作符
4.8 sizeof 操作符
sizeof 操作符在编译时刻计算,因此被看作常量表达式,它可以用在任何需要常量表达式的地方,如数组的维数或模板的非类型参数。
例如: int array[ sizeof( some_type_T) ];
4.9 new 和 delete 表达式
4.10 逗号操作符
4.11 位操作符
标准库提供了一个 bitset 类,它支持位向量的类抽象,它封装了位向量的语义。
位操作两种方式:使用 bitset 类,或者直接按位操作整值数据类型。
4.12 bitset 操作
#include <bitset>
4.13 优先级
4.14 类型转换
1 。隐式类型转换
2 。算术转换
3 。显式转换: static_cast dynamic_cast const_cast reinterpret_cast
有时 void* 型的指针被称为泛型指针,因为它可以指向任意数据类型的指针。不存在从 void* 型指针到特殊类型的指针之间的自动转换。
double dval;
int ival;
ival += dval;
ival += static_cast< int > ( dval ); // 消除了 ival 从 int 型到 double 型的不必要提升!
编译器隐式执行的任何类型转换都可以由 static_cast 显式完成。
reinterpret_cast 通常对于操作数的位模式执行一个比较低层次的重新解释。
dynamic_cast 支持在运行时刻识别由指针或引用指向的类对象。
4 。旧式强制类型转换
4.15 栈类实例
5 语句
程序最小的独立单元是语句( statement )。
5.1 简单语句和复合语句
5.2 声明语句
在 C 中,对象的定义并不被视为 C 语言的语句,块中的所有对象定义必须出现在任何程序语句之前。出于这种需要, C 程序员使自己习惯于在每个当前块的顶部定义全部对象。
而在 C++ 中,对象的定义是一条语句。
局部声明的意义:
1 。使程序更易于理解;
2 。声明的局部性使我们能够把初始化的开销分摊到函数或语句快中;
3 。提高函数性能,减少不必要的构造析构函数对。
5.3 if 语句
else 子句与“最后出现的未被匹配的 if 子句”相匹配。
pair 类――存储两个相关的值。
如果错误发生了,最重要的是接受发生错误的事实,并保持警惕,在条件允许的情况下尽可能严格的测试和检查代码。
5.4 switch 语句
关键字 case 后面的值必须是一种整数类型的常量表达式。
switch(std1::i)
{
case 0 :
// 非法的,必须包含在一个语句块中
// int i = 0;
{
int i = 0;
}
break;
case 1 :
break;
}
说明:把一条声明语句放在 case 或 default 相关联的语句中是非法的,除非它放在一个语句块中。原因:如果一个定义没有被包围在一个语句块中,那么它在 case 标签之间是可见的,但是只有当定义它的 case 标签被执行时,它才能被初始化,因此需要一个语句块来保证名字时可见地 。
5.5 for 循环语句
5.6 while 循环
5.7 do while 语句
不象其他循环结构, do while 循环的条件(即 condition 部分)不支持对象定义,不能写:
do {
// ……
}while( int foo = get_foo()); // 错误!!!
5.8 break 语句
5.9 continue 语句
5.10 goto 语句
5.11 链表示例
为了防止初始化和赋值,可以把拷贝构造函数和拷贝赋值操作符声明为私有成员。