c++ 箭头符号怎么打_C++Primer笔记(2)

数组的长度是固定的。数组一经创建,就不允许添加新的元素。指针则可以像迭代器一样用于遍历和检查数组中的元素。

一、数组

与 vector 类型相比,数组的显著缺陷在于:数组的长度是固定的,而且程序员无法知道一个给定数组的长度。数组没有获取其容量大小的 size 操作,也不提供 push_back 操作在其中自动添加元素。如果需要更改数组的长度,程序员只能创建一个更大的新数组,然后把原数组的所有元素复制到新数组空间中去。与使用标准 vector 类型的程序相比,依赖于内置数组的程序更容易出错而且难于调试。

1、数组定义及初始化

数组的维数必须用值大于等于1的常量表达式定义。而此常量表达式只能包含整型字面值常量、枚举常量,或用常量表达式初始化的整型const对象。非 const 变量以及要到运行阶段才知道其值的 const变量都不能用于定义数组的维数。

const unsigned sz = get_size(); // const value not known until run timeint test_scores[get_size()]; // error: non const expressionint vals[sz]; // error: size not known until run time

如果没有显式提供元素初值,则数组元素会像普通变量一样初始化:

• 在函数体外定义的内置数组,其元素均初始化为 0。

• 在函数体内定义的内置数组,其元素无初始化。

• 不管数组在哪里定义,如果其元素为类类型,则自动调用该类的默认构造函数进行初始化; 如果该类没有默认构造函数,则必须为该数组的元素提供显式初始化。

特殊的字符数组

char ca1[] = {'C', '+', '+'}; // no null

char ca2[] = {'C', '+', '+', '\0'}; // explicit null

char ca3[] = "C++"; // null terminator added automatically

ca1 的维数是 3,而 ca2 和 ca3 的维数则是 4。使用一组字符字面值初始

化字符数组时,一定要记得添加结束字符串的空字符。

const char ch3[6] = "Daniel"; // error: Daniel is 7 elements

不允许数组直接复制和赋值

数组元素可用下标或指针来访问,数组元素也是从 0 开始计数.

“缓冲区溢出(buffer overflow)”错误。当我们在编程时没有检查下标,并且引用了越出数组或其他类似数据结构边界的元素时,就会导致这类错误。

二、指针

指针是指向某种类型对象的复合数据类型,是用于数组的迭代器:指向数组中的一个元素。在指向数组元素的指针上使用解引用操作符 *(dereference operator)和自增操作符 ++(increment operator),与在迭代器上的用法类似。

对指针进行解引用操作,可获得该指针所指对象的值。而当指针做自增操作时,则移动指针使其指向数组中的下一个元素。

指针保存的是另一个对象的地址

1、指针变量定义

vector *pvec; // pvec can point to a vector

int *ip1, *ip2; // ip1 and ip2 can point to an int

string *pstring; // pstring can point to a string

double *dp; // dp can point to a double

2、指针可能的取值

有效的指针必然是以下三种状态之一:保存一个特定对象的地址;指向某个对象后面的另一对象;或者是 0 值。

int ival = 1024;

int *pi = 0; // pi initialized to address no object

int *pi2 = & ival; // pi2 initialized to address of ival

int *pi3; // ok, but dangerous, pi3 is uninitialized

pi = pi2; // pi and pi2 address the same object, e.g. ival

pi2 = 0; // pi2 now addresses no object

int *pi4 = NULL; // ok: equivalent to int *pi4 = 0;

如果必须分开定义指针和其所指向的对象,则将指针初始化为 0。因为编译器可检测出 0 值的指针,程序可判断该指针并未指向一个对象。

3、对指针进行初始化或赋值只能使用以下四种类型的值:

1. 0 值常量表达式,例如,在编译时可获得 0 值的整型 const对象或字面值常量 0。

2. 类型匹配的对象的地址。

3. 另一对象末的下一地址。

4. 同类型的另一个有效指针。

由于指针的类型用于确定指针所指对象的类型,因此初始化或赋值时必须保证类型匹配。

4、void* 指针

C++ 提供了一种特殊的指针类型 void*,它可以保存任何类型对象的地址:

double obj = 3.14;

double *pd = &obj;

// ok: void* can hold the address value of any data pointer type

void *pv = &obj; // obj can be an object of any type

pv = pd; // pd can be a pointer to any type

void* 表明该指针与一地址值相关,但不清楚存储在此地址上的对象的类型。

void* 指针只支持几种有限的操作:与另一个指针进行比较;向函数传递void* 指针或从函数返回 void* 指针;给另一个 void* 指针赋值。不允许使用void* 指针操纵它所指向的对象。

5、给指针赋值或通过指针进行赋值

如果对左操作数进行解引用,则修改的是指针所指对象的值;如果没有使用解引用操作,则修改的是指针本身的值。如图所示

6、指针和引用区别

1、引用总是指向某个对象,定义引用时没有初始化是错误的。而指针可以为空,可以不初始化。

2、给引用赋值修改的是该引用所关联的对象的值,而并不是使引用与另一个对象关联。引用一经初始化,就始终指向同一个特定对象。而指针可以指向别的对象。

int ival = 1024, ival2 = 2048;

int *pi = &ival, *pi2 = &ival2;

pi = pi2; // pi now points to ival2

赋值结束后,pi 所指向的 ival 对象值保持不变,赋值操作修改了 pi 指针的值,使其指向另一个不同的对象。现在考虑另一段相似的程序,使用两个引用赋值:

int &ri = ival, &ri2 = ival2;

ri = ri2; // assigns ival2 to ival

这个赋值操作修改了 ri 引用的值 ival 对象,而并非引用本身。赋值后,这两个引用还是分别指向原来关联的对象,此时这两个对象的值相等。

7、指向指针的指针

指针本身也是可用指针指向的内存对象。指针占用内存空间存放其值,因此指针的存储地址可存放在指针中。

int ival = 1024;

int *pi = &ival; // pi points to an int

int **ppi = π // ppi points to a pointer to int

对象可表示为:

为了真正地访问到 ival 对象,必须对 ppi 进行两次解引用:

cout << "The value of ival\n"

<< "direct value: " << ival << "\n"

<< "indirect value: " << *pi << "\n"

<< "doubly indirect value: " << **ppi

<< endl;

这段程序用三种不同的方式输出 ival 的值。首先,采用直接引用变量的方式输出;然后使用指向 int 型对象的指针 pi 输出;最后,通过对 ppi 进行两次解引用获得 ival 的特定值。

8、使用指针访问数组元素

int ia[] = {0,2,4,6,8};

int *ip = ia; // ip points to ia[0]

9、指针的算术操作

ip = ia; // ok: ip points to ia[0]

int *ip2 = ip + 4; // ok: ip2 points to ia[4], the last element in ia

C++ 还支持对这两个指针做减法操作:

ptrdiff_t n = ip2 - ip; // ok: distance between the pointers

结果是 4,这两个指针所指向的元素间隔为 4 个对象。与 size_t 类型一样,ptrdiff_t 也是一种与机器相关的类型,在 cstddef 头文件中定义。size_t 是unsigned 类型,而 ptrdiff_t 则是 signed 整型。

在指针上加一个整型数值,其结果仍然是指针。允许在这个结果上直接进行解引用操作,而不必先把它赋给一个新指针:

int last = *(ia + 4); // ok: initializes last to 8, the value of ia[4]

由于加法操作和解引用操作的 优先级不同,上述表达式中的圆括号是必要的。

使用下标访问数组时,实际上是使用下标访问指针:

int ia[] = {0,2,4,6,8};

int i = ia[0]; // ia points to the first element in ia

C++ 允许计算数组或对象的超出末端的地址,但不允许对此地址进行解引用操作。

10、指针与const

但是如果指针指向const 对象,则不允许用指针来改变其所指的 const 值。

const double *cptr; // cptr may point to a double that is constdouble dval = 3.14159; // dval is not constcptr = &dval; // ok: but can't change dval through cptr*cptr = 3.14159; // error: cptr is a pointer to constdouble *ptr = &dval; // ok: ptr points at non-const double*ptr = 2.72; // ok: ptr is plain pointercout << *cptr; // ok: prints 2.72

string const s1; // s1 and s2 have same type,const string s2; // they're both strings that are const

指向 const 的指针 cptr 实际上指向了一个非 const 对象。尽管它所指的对象并非 const,但仍不能使用 cptr 修改该对象的值。本质上来说,由于没有方法分辩 cptr 所指的对象是否为 const,系统会把它所指的所有对象都视为 const。

如果指向 const 的指针所指的对象并非 const,则可直接给该对象赋值或间接地利用普通的非 const 指针修改其值:毕竟这个值不是 const。重要的是要记住:不能保证指向 const 的指针所指对象的值一定不可修改。如果把指向 const 的指针理解为“自以为指向 const 的指针”,这可能会对理解有所帮助。

在实际的程序中,指向 const 的指针常用作函数的形参。将形参定义为指向 const 的指针,以此确保传递给函数的实际对象在函数中不因为形参而被修改。

11、const指针

本身的值不能修改,这就意味着不能使 curErr 指向其他对象。任何企图给 const 指针赋值的行为(即使给curErr 赋回同样的值)都会导致编译时的错误:

int errNumb = 0;

int *const curErr = &errNumb; // curErr is a constant pointer

curErr 指向一个普通的非常量 int 型对象 errNumb,则可使用 curErr 修改该对象的值:

*curErr = 0; // ok: reset value of the object to which curErris bound

12、指向 const 对象的 const 指针

const double pi = 3.14159;

// pi_ptr is const and points to a const object

const double *const pi_ptr = π

既不能修改 pi_ptr 所指向对象的值,也不允许修改该指针的指向(即 pi_ptr 中存放的地址值)。

13、指针与typedef

typedef string *pstring;

const pstring cstr;

请问 cstr 变量是什么类型?

简单的回答是 const pstring 类型的指针。

进一步问:const pstring 指针所表示的真实类型是什么?很多人都认为真正的类型是:

const string *cstr; // wrong interpretation of const pstring cstr

也就是说,const pstring 是一种指针,指向 string 类型的 const 对象,但这是错误的。错误的原因在于将 typedef 当做文本扩展了。声明 const pstring 时,const 修饰的是 pstring 的类型,这是一个指针。因此,该声明语句应该是把cstr 定义为指向 string 类型对象的 const 指针,这个定义等价于:

// cstr is a const pointer to string

string *const cstr; // equivalent to const pstring cstr

三、C 风格字符串的标准库函数

要使用这些标准库函数,必须包含相应的 C 头文件:cstring 是 string.h 头文件的 C++ 版本,而 string.h 则是 C 语言提供的标准库。

四、动态数组

1、创建动态数组

虽然数组长度是固定的,但动态分配的数组不必在编译时知道其长度,可以(通常也是)在运行时才确定数组长度。与数组变量不同,动态分配的数组将一直存在,直到程序显式释放它为止。

每一个程序在执行时都占用一块可用的内存空间,用于存放动态分配的对象,此内存空间称为程序的 自由存储区或 堆。

2、动态数组定义

动态分配数组时,只需指定类型和数组长度,不必为数组对象命名,new 表达式返回指向新分配数组的第一个元素的指针:

int *pia = new int[10]; // array of 10 uninitialized ints

在自由存储区中创建的数组对象是没有名字的,程序员只能通过其地址间接地访问堆中的对象。

3、初始化动态分配的数组

动态分配数组时,如果数组元素具有类类型,将使用该类的默认构造函数实现初始化;如果数组元素是内置类型,则无初始化

string *psa = new string[10]; // array of 10 empty strings

int *pia = new int[10]; // array of 10 uninitialized ints

第一个数组是 string类型,分配了保存对象的内存空间后,将调用 string 类型的默认构造函数依次初始化数组中的每个元素。第二个数组则具有内置类型的元素,分配了存储 10个 int 对象的内存空间,但这些元素没有初始化。

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

int *pia2 = new int[10] (); // array of 10 uninitialized ints

圆括号要求编译器对数组做值初始化,在本例中即把数组元素都设置为 0。

4、const 对象的动态数组

如果我们在自由存储区中创建的数组存储了内置类型的 const 对象,则必须为这个数组提供初始化:因为数组元素都是 const 对象,无法赋值。实现这个要求的唯一方法是对数组做值初始化:

const int *pci_bad = new const int[100]; // error: uninitialized const array

const int *pci_ok = new const int[100](); // ok: value-initialized const array

当然,已创建的常量元素不允许修改——因此这样的数组实际上用处不大。5

5、允许动态分配空数组

之所以要动态分配数组,往往是由于编译时并不知道数组的长度。我们可以编写如下代码

size_t n = get_size(); // get_size returns number of elements needed

int* p = new int[n];

for (int* q = p; q != p + n; ++q)

/* process the array */ ;

计算数组长度,然后创建和处理该数组。有趣的是,如果 get_size 返回 0 则会怎么样?答案是:代码仍然正确执行。C++ 虽然不允许定义长度为 0 的数组变量,但明确指出,调用 new 动态创建长度为 0 的数组是合法的:

char arr[0]; // error: cannot define zero-length array

char *cp = new char[0]; // ok: but cp can't be dereferenced

用 new 动态创建长度为 0 的数组时,new 返回有效的非零指针。该指针与new 返回的其他指针不同,不能进行解引用操作,因为它毕竟没有指向任何元素。而允许的操作包括:比较运算,因此该指针能在循环中使用;在该指针上加(减)0;或者减去本身,得 0 值。

6、动态空间释放

delete [] pia;

该语句回收了 pia 所指向的数组,把相应的内存返还给自由存储区。

回收数组时缺少空方括号对,至少会导致运行时少释放了内存空间,从而产生内存泄漏(memory leak)。对于某些系统和/或元素类型,有可能会带来更严重的运行时错误。因此,在释放动态数组时千万别忘了方括号对。

7、混合使用标准库类 string 和 C

string st3("Hello World"); // st3 holds Hello World

通常,由于 C 风格字符串与字符串字面值具有相同的数据类型,而且都是以空字符 null 结束,因此可以把 C 风格字符串用在任何可以使用字符串字面值的地方:

• 可以使用 C 风格字符串对 string 对象进行初始化或赋值。

• string 类型的加法操作需要两个操作数,可以使用 C 风格字符串作为其中的一个操作数,也允许将 C 风格字符串用作复合赋值操作的右操作数。

反之则不成立:在要求 C 风格字符串的地方不可直接使用标准库 string 类型对象。例如,无法使用 string 对象初始化字符指针:

char *str = st2; // compile-time type error

但是,string 类提供了一个名为 c_str 的成员函数,以实现我们的要求:

char *str = st2.c_str(); // almost ok, but not quite

c_str 函数返回 C 风格字符串,其字面意思是:“返回 C 风格字符串的表示方法”,即返回指向字符数组首地址的指针,该数组存放了与 string 对象相同的内容,并且以结束符 null 结束。

如果 c_str 返回的指针指向 const char 类型的数组,则上述初始化失败,这样做是为了避免修改该数组。正确的初始化应为:

const char *str = st2.c_str(); // ok

c_str 返回的数组并不保证一定是有效的,接下来对 st2 的操作有可能会改变 st2 的值,使刚才返回的数组失效。如果程序需要持续访问该数据,则应该复制 c_str 函数返回的数组。

8、数组初始化vector

使用数组初始化 vector 对象,必须指出用于初始化式的第一个元素以及数组最后一个元素的下一位置的地址:

const size_t arr_size = 6;

int int_arr[arr_size] = {0, 1, 2, 3, 4, 5};

// ivec has 6 elements: each a copy of the corresponding element in int_arr

vector ivec(int_arr, int_arr + arr_size);

传递给 ivec 的两个指针标出了 vector 初值的范围。第二个指针指向被复制的最后一个元素之后的地址空间。被标出的元素范围可以是数组的子集:

// copies 3 elements: int_arr[1], int_arr[2], int_arr[3]

vector ivec(int_arr + 1, int_arr + 4);

9、多维数组

int ia[3][4] = { /* 3 elements, each element is an array of size 4 */

{0, 1, 2, 3} , /* initializers for row indexed by 0 */

{4, 5, 6, 7} , /* initializers for row indexed by 1 */

{8, 9, 10, 11} /* initializers for row indexed by 2 */

};

// equivalent initialization without the optional nested braces for each row

int ia[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};

如果表达式只提供了一个下标,则结果获取的元素是该行下标索引的内层数组。如 ia[2] 将获得 ia 数组的最后一行,即这一行的内层数组本身,而并非该数组中的任何元素。

10、多维数组与指针

int ia[3][4]; // array of size 3, each element is an array of

ints of size 4

int (*ip)[4] = ia; // ip points to an array of 4 ints

ip = &ia[2]; // ia[2] is an array of 4 ints

*ip 是 int[4] 类型——即 ip 是一个指向含有 4 个元素的数组的指针。

11、int *p[4]和int (*q)[4]

int *p[4]------p是一个指针数组,每一个指针都指向一个int型数据。

int (*q)[4]---- -q是一个指针,指向int[4]的数组。

int *p[4]的存储结构如下:

int (*q)[4]。它首先是个指针,即*q,剩下的“int [4]”作为补充说明,即说明指针q指向一个长度为4的数组。int (*q)[4]的存储结构如下:

int a[2][4]={{2,5,6,8},{22,55,66,88}};

int c[4]={5,8,9,4};

int d[3]={23,12,443};

int *p[4],(*q)[4];

q=a;

*p=c;

*(p+1)=d;

则int *p[4]和int (*q)[4]的存储数据为:

五、运算符

1、运算符优先级

括号、下标、->、.(成员)最高

单目比双目高

算术双目比其他双目高

移位运算(<>)高于关系运算(,>=)

关系运算高于按位运算(与,或,异或)

按位运算高于逻辑运算(&&,||)

三目的只有条件运算(a>b?a:b),低于逻辑运算

赋值运算(=)仅比顺序运算高(,)。

2、<

若 IO 表达式的操作数包含了比 IO 操作符优先级低的操作符,相关的优先级别将影响书写该表达式的方式。通常需使用圆括号强制先实现右结合:

cout << 42 + 10; // ok, + has higher precedence, so the sum is printed

cout << (10 < 42); // ok: parentheses force intended grouping; prints 1

cout << 10 < 42; // error: attempt to compare cout to 42!

<

3、点操作符和箭头操作符

点操作符用于获取类类型对象的成员:

item1.same_isbn(item2); // run the same_isbn member of item1

如果有一个指向 Sales_item 对象的指针(或迭代器),则在使用点操作符前,需对该指针(或迭代器)进行解引用:

Sales_item *sp = &item1;

(*sp).same_isbn(item2); // run same_isbn on object to which sp points

注意必须用圆括号把解引用括起来,因为解引用的优先级低于点操作符。如果漏掉圆括号,则这段代码的含义就完全不同了:

// run the same_isbn member of sp then dereference the result!

*sp.same_isbn(item2); // error: sp has no member named same_isbn

这个表达式企图获得 sp 对象的 same_isbn 成员。等价于:

*(sp.same_isbn(item2)); // equivalent to *sp.same_isbn(item2);

然而,sp 是一个没有成员的指针;这段代码无法通过编译。

因为编程时很容易忘记圆括号,而且这类代码又经常使用,所以 C++ 为在点操作符后使用的解引用操作定义了一个同义词:箭头操作符(->)。假设有一个指向类类型对象的指针(或迭代器),下面的表达式相互等价:

(*p).foo; // dereference p to get an object and fetch its member named foo

p->foo; // equivalent way to fetch the foo from the object to which p points

sp->same_isbn(item2); // equivalent to (*sp).same_isbn(item2)

六、new和delete

定义变量时,必须指定其数据类型和名字。而动态创建对象时,只需指定其数据类型,而不必为该对象命名。取而代之的是,new 表达式返回指向新创建对象的指针,我们通过该指针来访问此对象:

int i; // named, uninitialized int variable

int *pi = new int; // pi points to dynamically allocated,

// unnamed, uninitialized int

int *pi = new int(); // pi points to an int value-initialized to 0

这个 new 表达式在自由存储区中分配创建了一个整型对象,并返回此对象的地址,并用该地址初始化指针 pi。

int *pi = new int(1024); // object to which pi points is 1024

string s(10, '9'); // value of s is "9999999999"

string *ps = new string(10, '9'); // *ps is "9999999999"

int i;

242

int *pi = &i;

string str = "dwarves";

double *pd = new double(33);

delete str; // error: str is not a dynamic object

delete pi; // error: pi refers to a local

delete pd; // ok

值得注意的是:编译器可能会拒绝编译 str 的 delete 语句。编译器知道str 并不是一个指针,因此会在编译时就能检查出这个错误。第二个错误则比较隐蔽:通常来说,编译器不能断定一个指针指向什么类型的对象,因此尽管这个语句是错误的,但在大部分编译器上仍能通过。

删除指针后,该指针变成 悬垂指针。悬垂指针指向曾经存放对象的内存,但该对象已经不再存在了。悬垂指针往往导致程序错误,而且很难检测出来。一旦删除了指针所指向的对象,立即将指针置为 0,这样就非常清楚地表明指针不再指向任何对象。

指针转换

在使用数组时,大多数情况下数组都会自动转换为指向第一个元素的指针:

int ia[10]; // array of 10 ints

int* ip = ia; // convert ia to pointer to first element

不将数组转换为指针的例外情况有:数组用作取地址(&)操作符的操作数或 sizeof 操作符的操作数时,或用数组对数组的引用进行初始化时,不会将数组转换为指针。

C++ 还提供了另外两种指针转换:指向任意数据类型的指针都可转换为void* 类型;整型数值常量 0 可转换为任意指针类型

七、显示转换

即强制类型转换( cast),包括以下列名字命名的强制类型转换操作符: static_cast、 dynamic_cast、 const_cast 和 reinterpret_cast。

static_cast

编译器隐式执行的任何类型转换都可以由 static_cast 显式完成:

double d = 97.0;

// cast specified to indicate that the conversion is intentional

char ch = static_cast(d);

当需要将一个较大的算术类型赋值给较小的类型时,使用强制转换非常有用。此时,强制类型转换告诉程序的读者和编译器:我们知道并且不关心潜在的精度损失。

dynamic_cast 通常在基类和派生类之间转换时使用

dynamic_cast(a)

在运行期,会检查这个转换是否可能。完成类层次结构中的提升。T必须是一个指针、引用或无类型的指针。a必须是决定一个指针或引用的表达式。

dynamic_cast 仅能应用于指针或者引用,不支持内置数据类型表达式dynamic_cast(a) 将a值转换为类型为T的对象指针。如果类型T不是a的某个基类型,该操作将返回一个空指针。

const_cast (主要针对const和volatile的转换)

const_cast ,顾名思义,将转换掉表达式的 const 性质。例如,假设有函数 string_copy,只有唯一的参数,为 char* 类型,我们对该函数只读不写。在访问该函数时,最好的选择是修改它让它接受 const char* 类型的参数。如果不行,可通过 const_cast 用一个 const 值调用string_copy 函数:

const char *pc_str;

char *pc = string_copy(const_cast(pc_str)); //将pc_str转换为char*类型

只有使用 const_cast 才能将 const 性质转换掉。

reinterpret_cast: 用于进行没有任何关联之间的转换,比如一个字符指针转换为一个整形数。

reinterpret_cast(a)

编译器在编译期处理

任何指针都可以转换成其它类型的指针,T必须是一个指针、引用、算术类型、指向函数的指针或指向一个类成员的指针。

旧式强制转换符号有下列两种形式:

type (expr); // Function-style cast notation

(type) expr; // C-language-style cast notation

八、try-catch

异常就是运行时出现的不正常,例如运行时耗尽了内存或遇到意外的非法输入。异常存在于程序的正常功能之外,并要求程序立即处理。

异常机制提供程序中错误检测与错误处理部分之间的通信。C++ 的异常处理中包括:

1. throw 表达式,错误检测部分使用这种表达式来说明遇到了不可处理的错误。可以说,throw 引发了异常条件。

2. try 块,错误处理部分使用它来处理异常。try 语句块以 try 关键字开始,并以一个或多个 catch 子句结束。在 try 块中执行的代码所抛出(throw)的异常,通常会被其中一个 catch 子句处理。由于它们“处理”异常,catch 子句也称为 处理代码。

3. 由标准库定义的一组 异常类,用来在 throw 和相应的 catch 之间传递有关的错误信息。

try 块的通用语法形式是:

try {

program-statements

} catch (exception-specifier) {

handler-statements

} catch (exception-specifier) {

handler-statements

} //...

try 块以关键字 try 开始,后面是用花括号起来的语句序列块。try 块后面是一个或多个 catch 子句。每个 catch 子句包括三部分:关键字 catch,圆括号内单个类型或者单个对象的声明——称为 异常说明符,以及通常用花括号括起来的语句块。如果选择了一个 catch 子句来处理异常,则执行相关的块语句。一旦 catch 子句执行结束,程序流程立即继续执行紧随着最后一个 catch 子句的语句。

标准异常

标准库异常类定义在四个头文件中:

1. exception 头文件定义了最常见的异常类,它的类名是 exception。这个类只通知异常的产生,但不会提供更多的信息。

2、在 头文件中定义的标准异常类

3、new 头文件定义了 bad_alloc 异常类型,提供因无法分配内在而由 new抛出的异常。

4、 type_info 头文件定义了 bad_cast 异常类型,这种类型将第 18.2 节讨论

一个常见的调试技术是使用 NDEBUG 预处理变量以及 assert 预处理宏。assert 宏是在 cassert 头文件中定义的,所有使用 assert 的文件都必须包含这个头文件。

预处理宏有点像函数调用。assert 宏需要一个表达式作为它的条件:

assert(expr)

只要 NDEBUG 未定义,assert 宏就求解条件表达式 expr,如果结果为false,assert 输出信息并且终止程序的执行。如果该表达式有一个非零(例如,true)值,则 assert 不做任何操作。

九、局部对象

在C++中,每个名字都有作用域,每个对象都有生命期。名字的作用域指的是知道该名字的程序文本区。对象的生命期则是在程序执行过程中对象存在的时间。

在函数中定义的形参和变量的名字只位于函数的作用域中:这些名字只在函数体中可见。通常,变量名从声明或定义的地方开始到包围它的作用域结束处都是可用的。

1、自动对象

只有当定义它的函数被调用时才存在的对象称为 自动对象自动对象在每次调用函数时创建和撤销。

局部变量所对应的自动对象在函数控制经过变量定义语句时创建。如果在定义时提供了初始化式,那么每次创建对象时,对象都会被赋予指定的初值。对于未初始化的内置类型局部变量,其初值不确定。当函数调用结束时,自动对象就会撤销。

形参也是自动对象。形参所占用的存储空间在调用函数时创建,而在函数结束时撤销。

2、静态局部对象

一个变量虽然位于函数体内,但生命期跨越了这个函数的多次调用,将其定义为static(静态的)。

static 局部对象确保不迟于在程序执行流程第一次经过该对象的定义语句时进行初始化。

十、内联函数

例:一个返回两个字符串中较短的字符串的函数

const string &shorterString(const string &s1, const string &s2)

{

return s1.size() < s2.size() ? s1 : s2;

}

为这个操作定义一个函数的好处是:

• 阅读和理解函数 shorterString 的调用,要比读一条用等价的条件表达式取代函数调用表达式并解释它的含义要容易得多。

• 如果需要做任何修改,修改函数要比找出并修改每一处等价表达式容易得多。

• 使用函数可以确保统一的行为,每个测试都保证以相同的方式实现。

• 函数可以重用,不必为其他应用重写代码。

缺点是:调用函数比求解等价表达式要慢得多。在大多数的机器上,调用函数都要做很多工作;调用前要先保存寄存器,并在返回时恢复;复制实参;程序还必须转向一个新位置执行。

inline 函数避免函数调用的开销(只需在函数前加个inline)

将 shorterString 定义为内联函数,则调用:

cout << shorterString(s1, s2) << endl;

在编译时将展开为:

cout << (s1.size() < s2.size() ? s1 : s2)

<< endl;

从而消除了把 shorterString 写成函数的额外执行开销。

内联机制适用于优化小的、只有几行的而且经常被调用的函数。大多数的编译器都不支持递归函数的内联

内联函数应该在头文件中定义

inline 函数的定义对编译器而言必须是可见的,以便编译器能够在调用点内联展开该函数的代码。此时,仅有函数原型是不够的。

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

在头文件中加入或修改 inline 函数时,使用了该头文件的所有源文件都必须重新编译。

十一、类的构造函数

构造函数名字和类型相同,无返回值

构造函数和初始化列表

在冒号和花括号之间的代码称为 构造函数的初始化列表。构造函数的初始化列表为类的一个或多个数据成员指定初值。它跟在构造函数的形参表之后,以冒号开关。构造函数的初始化式是一系列成员名,每个成员后面是括在圆括号中的初值。多个成员的初始化用逗号分隔。

Sales_item(): units_sold(0), revenue(0.0) { }

由编译器创建的默认构造函数通常称为 默认构造函数

通常将类的声明放置在头文件中。大多数情况下,在类外定义的成员函数则置于源文件中。

构造函数种类及实例可参考:

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

有些看起来不相同的形参表本质上是相同的:

// each pair declares the same functionRecord lookup(const Account &acct);

Record lookup(const Account&); // parameter names are ignoredtypedef Phone Telno;

Record lookup(const Phone&);

Record lookup(const Telno&); // Telno and Phone are the same typeRecord lookup(const Phone&, const Name&);

// default argument doesn't change the number of parametersRecord lookup(const Phone&, const Name& = "");

// const is irrelevent for nonreference parametersRecord lookup(Phone);

Record lookup(const Phone); // redeclaration

给函数传递实参遵循变量初始化的规则。非引用类型的形参以相应实参的副本初始化。对(非引用)形参的任何修改仅作用于局部副本,并不影响实参本身。

复制庞大而复杂的值有昂贵的开销。为了避免传递副本的开销,可将形参指定为引用类型。对引用形参的任何修改会直接影响实参本身。应将不需要修改相应实参的引用形参定义为 const 引用。

十二、标准IO库

1、IO 标准库类型和头文件

2、IO 对象不可复制或赋值

ofstream out1, out2;

out1 = out2; // error: cannot assign stream objects

// print function: parameter is copied

ofstream print(ofstream);

out2 = print(out2); // error: cannot copy stream objects

3、输出缓冲区的管理

每个 IO 对象管理一个缓冲区,用于存储程序读写的数据。如有下面语句:

os << "please enter a value: ";

系统将字符串字面值存储在与流 os 关联的缓冲区中。下面几种情况将导致缓冲区的内容被刷新,即写入到真实的输出设备或者文件:

1. 程序正常结束。作为 main 返回工作的一部分,将清空所有输出缓冲区。

2. 在一些不确定的时候,缓冲区可能已经满了,在这种情况下,缓冲区将会在写下一个值之前刷新。

3. 用操纵符(第 1.2.2 节)显式地刷新缓冲区,例如行结束符 endl。

4. 在每次输出操作执行完后,用 unitbuf 操作符设置流的内部状态,从而清空缓冲区。

5. 可将输出流与输入流关联(tie)起来。在这种情况下,在读输入流时将刷新其关联的输出缓冲区。

输出缓冲区的刷新

cout << "hi!" << flush; // flushes the buffer; adds no datacout << "hi!" << ends; // inserts a null, then flushes the buffercout << "hi!" << endl; // inserts a newline, then flushes the buffer

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值