第一、二章 概述
- 编译C++程序时,编译器自动定义了一个预处理器名字__cplusplus,因此,我们可以根据它来判断该程序是否是C++程序,以便有条件地包含一些代码。例如:
#ifdef __cplusplus // 我们要编译 C++ extern "C" #endif int min( int, int );
- C++库名字 C库名字 区别
#include <cassert> using namespace std;
#include <assert.h>
相当于以上两行。 - 数组
- C++对数组类型提供的内置支持,仅限于读写单个元素,不支持对整个数组的操作。
- 数组不是C++语言的一等公民(first-class),它是从 C 语言中继承来的,反映了数据与对其进行操作的算法的分离。而这正是过程化程序设计的特征。
- 静态与动态内存分配,两个主要区别
- 1.访问方式:直接/间接
静态对象
是有名字
的变量,可直接对其进行操作;- 而
动态对象
是没有名字
的变量,惟一的访问方式是通过指针间接地访问。
- 2.分配与释放
- 静态对象的分配与释放由编译器自动处理,不需要做任何事情;
- 动态对象的分配与释放必须由程序员显式地管理(通过new和delete)
- 1.访问方式:直接/间接
- 内存泄漏(memory leak)
- 如果
动态分配的内存没有释放
,程序就会在结束时出现内存泄漏: - 即,一块动态分配的内存,我们不再拥有指向这块内存的指针,因此
无法将它返还给程序供以后重新使用
(现在大多数系统提供识别内存泄漏的工具)
- 如果
- private 和 public 控制对类成员的访问
- 出现在类体中公有 public 部分的成员,在一般程序的任何地方都可以访问它们
- 出现在私有 private 部分的成员,只能在
该类的成员函数
或友元
(friend)中被访问
- inline
- 考虑下面例子
IntArray array; int array_size = array.size();
- 还有 // 假设_size 是 public 的
int array_size = array._size;
- 显然 第一个例子需要一个函数调用 而第二个只需直接访问内存就行了
- 一般来说 函数调用比直接访问内存的开销要大得多 信息隐藏(类的公共接口与私有实现代码的分离)是否给程序的执行增加了额外负担呢?
- 幸运的是,在一般情况下 回答是 不。
- C++提供的方案是,size()作为非虚拟函数 由编译器处理并内联展开。
- 考虑下面例子
- 数据成员
- 被声明为 static 的数据成员
- 无论这个类的对象被定义了多少个,静态数据成员在程序中也只有一份.
- 这是在类的所有对象之间共享数据的一种方式。
- 保护protected,私有private
- 类的
保护
区域内的数据/成员函数,不提供给一般的程序,只提供给派生类。 - 基类的
私有
区域内的成员,只能供该类自己使用,连派生类都不能使用。
- 类的
- 被声明为 static 的数据成员
- 派生类对象的初始化过程 + 首先自动调用每个基类的构造函数来初始化相关的基类子对象 + 然后再执行派生类的构造函数
- 派生类 构造函数 是
必需
的,原因: + 基类的构造函数 并没有 被派生类继承(析构函数和拷贝赋值操作符同样也没有) + 派生类 IntArrayRC 构造函数 提供了向 基类IntArray 构造函数 传递参数的接口 - 为什么
不需要
提供 派生类拷贝构造函数
? + 14、17 章 构造函数 讲解 - 继承过来的 需要析构的 IntArray 成员 都由 IntArray 析构函数 来处理
- 类模板 的成员函数会怎么样呢? + 不是 所有的成员函数 都能自动地随 类模板 的 实例化 而被实例化 + 只有真正被程序 使用到的成员函数 才会被实例化 + 这一般发生在程序生成过程中的一个独立阶段,16.8节将详细讨论
- 异常
+ 不是在
发生
异常的地方恢复执行过程,而是在处理
异常(catch子句)的地方恢复执行过程。 + 如果,异常机制按照函数被调用的顺序回查每个函数直到 main()函数 仍然没有找到处理代码, - 那么它将调用标准库函数 terminate() 。缺省情况下,terminate()函数结束程序。
第三章 C++数据类型
- 变量 和 文字常量 都有存储区,并且有相关的类型 区别在于: - 变量 可寻址(addressable) - 文字常量 不可寻址
- 声明(declaration)不是定义(比如 关键字 extern), 不会引起内存分配. - 常用做法: - 不是 在每个使用对象的文件中都提供一个单独的声明, - 而是在一个头文件中声明这个对象, 然后 在需要声明该对象的时候 include这个头文件
- 初始化
- 如果一个变量是在
全局域
global scope 内定义的,那么系统提供初始值 0
- 如果变量是在
局部域
local scope 内定义的,或是通过new
动态分配的,则这些对象是未初始化
的,系统不会向它提供初始值 0。 - 除了类对象,
内置数据类型
也支持构造函数
语法,可将对象初始化为 0- 例如
int ival = int();
设置 ival 为 0
- 例如
- 如果一个变量是在
- 空指针
- void* 是 空类型指针,可以被
任何类型
数据指针 的地址 赋值 - 但 空指针 指向的 对象类型 未知,所以,不能 操作空类型指针 所指向的对象
- 只能传送该地址值 或者 将它与其他地址值作比较
- void* 是 空类型指针,可以被
- 字符串类型:C风格字符串 和 string类型
- “+” 可以完成 字符串(string类型)拼接
- 初始化:
- 将一个
C风格的字符串
赋给一个string对象
string s1; const char *pc = "a character array"; s1 = pc; // ok
- string 👉 C风格字符串:
const char *str = s1.c_str();
- 将一个
- const 限定修饰符
- 只读(read- only)
- 例子
const int bufSize = 512
- 例子
- 把变量定义为const常量 可以避免 意外 修改 对象
- 例如
if ( bufsize = 0 )// 错误 企图写入 const 对象
- 例如
- 常量在定义后就不能被修改 所以它必须被初始化
- 未初始化的常量定义,将导致编译错误:
const double pi; // 错误: 未初始化的常量
- 未初始化的常量定义,将导致编译错误:
- 只读(read- only)
- 指向常量的指针,
- 往往被用作函数参数,例如
int strcmp( const char *str1, const char *str2 );
- 一个 const指针,可以指向一个 const 或 非const 对象
- 指向
非const对象
的 const指针 curErr:- 不能赋给 curErr 其他的地址值,
- 但可以修改 curErr 指向的值
- 指向
const对象
的 const指针:- 指向对象的值,以及它的地址,都不能被改变
- 指向
- 往往被用作函数参数,例如
- 指针和引用 区别
- 引用 在定义后不能修改,所以 引用 必须初始化.
- 引用类型主要被用作函数的 形式参数
- 一个引用给另一个引用赋值,改变的是引用对象的值,而不是引用。(而一个指针给指针赋值,改变的是指针。)
- 假定有下列代码
int &ri = ival, &ri2 = ival2;
ri = ri2;
- 改变的是 ival 而不是引用本身。赋值之后,两个引用仍然指向原来的对象
- 假定有下列代码
- const
- const表达式:必须能在编译时刻计算出它的值。
- 对于 非 const 对象,系统只能在运行时刻访问它的值
- 字符串常量包含一个额外的终止空字符。比如:
// 错误: "Daniel"是 7 个元素 const char ch3[ 6 ] = "Daniel";