指针,引用和动态分配内存是 C/C++语言中最强大的特性,这些特性使得程序员能够直接操作计算机中非常珍贵的记忆体资源,进而对内存进行最大性能和高效的使用。 然而指针也是一把双刃剑,它也是 C/C++编程语言中最复杂和最难的特性。
指针之所以这么强大,是因为它允许你通过地址来访问和操作对应记忆体中存储的内容。但是指针也很难被驾驭,使用好的话确实能够大大提升性能,而用的 不好的话,也会导致很多问题,例如著名的内存泄漏和缓冲区溢出,这些 bug 会致使系统发生紊乱。一些新的编程语言(例如 Java 和 C#),将指针从它的语法 中去掉,通过提供自动内存管理的方式来避免使用指针导致的许多问题。
虽然你在编写 C/C++代码的时候可以不使用指针,但是在学习 C/C++的时候很难不提及指针。指针也许不是为新手和笨蛋而设计的。
1. 指针变量
计算机记忆体位置有一个地址,对应地址处保存数据。记忆体地址通常是一个数字(一般用十六进制表示),这个数字很难被程序员直接使用。通常一个地址位置 的容量是 8-bit(也就是 1-byte),里面可以存储整数,实数,字符或者字符串,这完全取决于程序员如何解析。
为了减轻程序员使用数字地址和解析数据的负担,早期的编程语言(例如 C 语言)中产生了一种新的变量——这种变量是一个被命名了的变量的位置,它可以存储一个特定类型的值。
取代数字地址的是用名字(或者标识符)直接关联到确定的地址上,而且变量类型(如int
, double
, char
)与之关联,从而简化了对数据的解析。
每个内存地址占 8 位(也就是 1 个字节),一个 4 个字节的int
值需要 4 个内存位置。一个 32 位的系统通常使用 32 位的地址。同样的,存储这个 32 位的地址也需要 4 个内存位置。
下面的插图形象的描绘了计算机内存地址,内存中数据,变量名,变量类型以及变量值之间的关系。
1.1 指针变量
指针变量(简称指针)基本上跟其他变量一样,都可以用来存放数据,但是跟普通变量不同的是,普通变量存储的是数值,而指针存放的是内存地址。
1.2 申明指针
指针在使用前必须先申明。申明指针的语法是在指针名前加上一个*
符号。指针必须跟类型关联。
例如:
需要注意的是,*
必须放在指针变量名前面,一个*
只作用于跟在它后面的指针变量名。*
在申明语句中不是一个操作符,仅仅表明跟在它后门的变量是一个指针变量。
例如:
指针变量名的命名规则:用"p"或者"ptr"作为前缀或后缀。
1.3 使用取址操作符(&)初始化指针
当你申明一个指针的时候,它并没有被初始化。也就是说,它指向一个不确定的非法地址,这是很危险的。你需要通过给它赋值为一个合法地址来对它进行初始化,而要完成这一 操作,需要使用取址操作符(&)。
取址操作符(&)作用于变量,返回该变量的地址。例如,如果number
是一个int
类型的变量,那么&number
返回的就是这个变量的地址。
如上图所示,int
型变量number
的起始地址是0x22ccec
,其中存放了一个int
值 88。表达式&number
返回该变量的地址,也就是0x22ccec
,然后赋值给了指针变量pNumber
。
取址操作符只能用在 RHS(right hand side).
1.4 间接寻址或解引操作符(\*)
间接寻址操作符(或解引操作符)(*
)作用于指针变量,返回存放在指针指向地址处的数据。例如,如果pNumber
是一个int
型指针,*pNumber
返回指针pNumber
指向的int
数值。
1int number = 88;
2int *pNumber = &number;
3cout << pNumber << endl;
4cout << *pNumber << endl;
5*pNumber = 99;
6cout << *pNumber << endl;
7cout << number << endl;
需要注意的是pNumber
存放的是内存地址,也就是说*pNumber
是指指针存放的地址处存放的数值,或者也可以说是指针指向的数值。
正如前面插图描绘的,变量直接引用数值,而指针通过内存地址间接引用数值。而这种间接引用被称为间接寻址或解引。
解引操作符既能被用于 RHS(temp = *pNumber
),也可以被用于 LHS(*pNumber = 99
).
注意:*
在申明语句中和表达式中有不同的含义,在申明语句中,它表示跟在它后门的变量是一个指针变量,而在表达式中,它指的是指针指向的数值。
1.5 指针也有类型
指针在申明的时候就要确定它所关联的类型。指针只能保存它所申明的类型的变量的地址。
1int i = 88;
2double d = 55.66;
3int *iPtr = &i;
4double *dPtr = &d;
5
6iPtr = &d; // ERROR, cannot hold address of different type
7dPtr = &i; // ERROR
8iPtr = i; // ERROR, pointer holds address of an int , NOT int value
9
10int j = 99;
11iPtr = &j; // You can change the address stored in a pointer.
示例
1#include <iostream>
2
3using namespace std;
4
5int main(int argc, char *argv[])
6{
7 int number = 88;
8 int *pNumber;
9 pNumber = &number;
10
11 cout << pNumber << endl;
12 cout << &number << endl;
13 cout << *pNumber << endl;
14 cout << number << endl;
15
16 *pNumber = 99;
17 cout << pNumber << endl;
18 cout << &number << endl;
19 cout << *pNumber << endl;
20 cout << number << endl;
21
22 cout << &pNumber << endl;
23 return 0;
24}
运行结果为:
1→ g++ test_pointer.cpp && ./a.out
20x7ffee123c04c
30x7ffee123c04c
488
588
60x7ffee123c04c
70x7ffee123c04c
899
999
100x7ffee123c040
注意:你得到的地址可能跟我得到的不同,操作系统将程序加载到可用的空闲地址,而不是固定的地址。
1.7 空指针
你可以将指针初始化为0
或者NULL
,那么它将指向空,我们称之为空指针。解引空指针会引发STATUS_ACCESS_VIOLATION
异常。
1int *iPtr = 0;
2cout << *iPtr << endl; // ERROR! STATUS_ACCESS_VIOLATION exception
3
4int *p = NULL;
2. 引用变量
C++增加了一种叫做引用变量(或者简称为引用)的特性。一个引用就是一个已有变量的别名。例如,假设给paul
起了个引用(别名)peter
,那么peter
和paul
都指的是同一个人。
引用的主要作用就是作为函数的形参以实现按引用传递(pass-by-reference)的特性。当一个引用变量传递给函数的时候,函数作用于该变量的原始值(而不是变量的拷贝)。对于该变量的操作 会同时反映到函数内和函数外部。
引用类似于指针,在一些情况下,引用可以取代指针,尤其是作为函数参数的时候。
2.1 引用(别名)(&)
回顾前面讲到的,C/C++使用&
符号在表达式中作为取址操作符,而 C++赋予了它额外的含义,在 C++的申明语句中,可以用&
申明一个引用变量。
当&
用于申明语句(包括函数形参)中的时候,它是类型标识符的一部分,用于申明一个引用变量。
1type &newName = existingName;
2// or
3type& newName = existingName;
4// or
5type & newName = existingName;
“newName"被称为"existingName"的引用或别名。此时,你可以通过"newName"或"existingName"访问变量。
1#include <iostream>
2
3using namespace std;
4
5int main(int argc, char *argv[])
6{
7 int number = 88;
8 int &refNumber = number;
9
10 cout << number << endl;
11 cout << refNumber << endl;
12
13 refNumber = 99;
14 cout << number << endl;
15 cout << refNumber << endl;
16
17 number = 55;
18 cout << number << endl;
19 cout << refNumber << endl;
20
21 return 0;
22}
编译运行结果为:
2.2 引用是如何工作的?
引用跟指针的工作原理一样,如图说是,引用保存的是变量的地址:
2.3 引用 vs. 指针
指针和引用是等价的,除了以下几种情况:
1 引用是一个地址的名字常量,声明的时候必须初始化
1int & iRef; // ERROR: 'iRef' declared as reference but not initialized
引用一旦建立,不能修改。
2 获取指针指向的数据,需要使用解引操作,而给指针赋值,需要用到取址操作符。而在引用中,引用和解引都是隐式的。
1#include <iostream>
2
3using namespace std;
4
5int main(int argc, char *argv[])
6{
7 int number1 = 88, number2 = 22;
8
9 // Create a pointer pointing to number1;
10 int *pNumber1 = &number1;
11 *pNumber1 = 99;
12 cout << *pNumber1 << endl;
13 cout << &number1 << endl;
14 cout << pNumber1 << endl;
15 cout << &pNumber1 << endl;
16
17 pNumber1 = &number2;
18
19 // Create a reference (alias) to number1
20 int &refNumber1 = number1;
21 refNumber1 = 11;
22 cout << refNumber1 << endl;
23 cout << &number1 << endl;
24 cout << &refNumber1 << endl;
25
26 // refNumber1 = &number2; // Error! Reference cannot be re-assigned
27
28 refNumber1 = number2;
29 number2++;
30 cout << refNumber1 << endl;
31 cout << number1 << endl;
32 cout << number2 << endl;
33 return 0;
34}
编译运行结果:
1→ g++ test_pointer_reference.cpp && ./a.out
299
30x7ffeeacc503c
40x7ffeeacc503c
50x7ffeeacc5030
611
70x7ffeeacc503c
80x7ffeeacc503c
922
1022
1123
引用为已有的变量提供了一个别名。引用包含了隐式的解引操作,所以不需要使用*
操作符来显式解引。而指针变量存储的是地址,你可以修改它存储的地址,访问指针指向的数据需要显式解引操作。引用可以被视为常量指针,它在申明的时候必须初始化,而且它的值不能被修改。
引用很接近于指针,在一些情况下,引用可以代替指针。引用允许你通过指针管理对象而不需要使用指针的语法。
2.4 引用参数 vs. 指针参数,两种实现 Pass-By-Reference 的方式
Pass-by-Value
在 C/C++中,默认情况下,函数参数是按值传递给函数的。也就是说,函数中接收的是参数的一份拷贝,在函数体内对参数的操作不会影响到函数体外部,换句话说,被呼叫的函数没有访问呼叫者的变量:
1#include <iostream>
2
3using namespace std;
4
5int square(int);
6
7int main(int argc, char *argv[])
8{
9 int number = 8;
10 cout << "In main():" << &number << endl;
11 cout << number << endl;
12 cout << square(number) << endl;
13 cout << number << endl;
14 return 0;
15}
16
17int square(int n) {
18 cout << "In square():" << &n << endl;
19 n *= n;
20 return n;
21}
编译运行:
通过指针参数实现 Pass-by-Reference
有时候,我们希望直接修改原始对象来避免没必要的拷贝,这样就需要使用到 pass-by-reference,下面是使用指针实现 pass-by-reference 的例子:
1#include <iostream>
2
3using namespace std;
4
5void square(int *);
6
7int main(int argc, char *argv[])
8{
9 int number = 8;
10 cout << "In main():" << &number << endl;
11 cout << number << endl;
12 square(&number);
13 cout << number << endl;
14 return 0;
15}
16
17void square(int *pNumber) {
18 cout << "In square():" << pNumber << endl;
19 *pNumber *= *pNumber;
20}
编译运行:
1→ g++ call_by_ref_with_pointer.cpp && ./a.out
2In main():0x7ffee23e20ac
38
4In square():0x7ffee23e20ac
564
译者注:
在 C/C++中,使用指针参数并不能称作Pass-by-Reference
,因为指针作为参数的实质还是call-by-value
,因为指针存放的是地址,
所以指针的拷贝中存放的也是同样的地址,对指针指向的数据的操作会改变到函数外部的数值。验证函数是否为call-by-ref
,实参的地址,内外是否相同:
1#include <iostream>
2
3using namespace std;
4
5void foo(int *pNumber);
6
7int main(int argc, char *argv[])
8{
9 int a = 10;
10 int *pNumber = &a;
11 cout << &pNumber << endl;
12 foo(pNumber);
13 return 0;
14}
15
16void foo(int *pNumber) {
17 cout << &pNumber << endl;
18}
运行结果为:
使用引用实现 Pass-by-reference
除了上面使用指针的方式实现 Pass-by-Reference,你还可以使用引用实现。
1#include <iostream>
2
3using namespace std;
4
5void square(int &);
6
7int main() {
8 int number = 8;
9 cout << "In main():" << &number << endl;
10 cout << number << endl;
11 square(number);
12 cout << number << endl;
13}
14
15void square(int &rNumber) {
16 cout << "In square():" << &rNumber << endl;
17 rNumber *= rNumber;
18}
“const” 参数
函数的形式参数如果被"const"修饰的话,那么实参在函数体内部是不能被修改的。 被"const"修饰的形参既能接收"const"类型的实参也能接收非"const"类型的实参,而一个非"const"类型的引用/指针参数,只能接收 非"const"类型的实参:
1#include <iostream>
2
3using namespace std;
4
5int squareConst(const int number) {
6 // number *= number; // error: assignment of read-only parameter
7 return number * number;
8}
9
10int squareNonConst(int number) {
11 number *= number;
12 return number;
13}
14
15int squareConstRef(const int & number) {
16 return number * number;
17}
18
19int squareNonConstRef(int * number) {
20 return number * number;
21}
22
23int main() {
24 int number = 8;
25 cout int constNumber = 9;
26 cout << squareConst(number) << endl;
27 cout << squareConst(constNumber) << endl;
28 cout << squareNonConst(number) << endl;
29 cout << squareNonConstRef(constNumber) << endl;
30 cout << squareConstRef(number) << endl;
31
32 cout << squareConstRef(constNumber) << endl;
33 cout << squareNonConstRef(number) << endl;
34 // cout << squareNonConstRef(constNumber) << endl;
35 // error: invalid initialization of reference of
36 // type 'int&' from expression of type 'const int'
37
38 return 0;
39}
2.5 函数的返回值
函数返回引用
函数的返回值既可以是引用也可以是指针,例如:
1/* Passing back return value using reference (TestPassByReferenceReturn.cpp) */
2#include <iostream>
3using namespace std;
4
5int & squareRef(int &);
6int * squarePtr(int *);
7
8int main() {
9 int number1 = 8;
10 cout << "In main() &number1: " << &number1 << endl; // 0x22ff14
11 int & result = squareRef(number1);
12 cout << "In main() &result: " << &result << endl; // 0x22ff14
13 cout << result << endl; // 64
14 cout << number1 << endl; // 64
15
16 int number2 = 9;
17 cout << "In main() &number2: " << &number2 << endl; // 0x22ff10
18 int * pResult = squarePtr(&number2);
19 cout << "In main() pResult: " << pResult << endl; // 0x22ff10
20 cout << *pResult << endl; // 81
21 cout << number2 << endl; // 81
22}
23
24int & squareRef(int & rNumber) {
25 cout << "In squareRef(): " << &rNumber << endl; // 0x22ff14
26 rNumber *= rNumber;
27 return rNumber;
28}
29
30int * squarePtr(int * pNumber) {
31 cout << "In squarePtr(): " << pNumber << endl; // 0x22ff10
32 *pNumber *= *pNumber;
33 return pNumber;
34}
不要返回函数体内本地变量的引用
1/* Test passing the result (TestPassResultLocal.cpp) */
2#include <iostream>
3using namespace std;
4
5int * squarePtr(int);
6int & squareRef(int);
7
8int main() {
9 int number = 8;
10 cout << number << endl; // 8
11 cout << *squarePtr(number) << endl; // ??
12 cout << squareRef(number) << endl; // ??
13}
14
15int * squarePtr(int number) {
16 int localResult = number * number;
17 return &localResult;
18 // warning: address of local variable 'localResult' returned
19}
20
21int & squareRef(int number) {
22 int localResult = number * number;
23 return localResult;
24 // warning: reference of local variable 'localResult' returned
25}
上面的程序有一个严重的逻辑错误,因为函数体内的本地变量的引用被函数返回。本地变量的作用于只在函数体内 ��������������� 放函数退出的时候,函数体内的本地变量就会被销毁。 GCC 编译器足够聪明,在编译上述代码的时候会给出警告(但不是错误)。
将动态申请的内存作为引用返回
1/* Test passing the result (TestPassResultNew.cpp) */
2#include <iostream>
3using namespace std;
4
5int * squarePtr(int);
6int & squareRef(int);
7
8int main() {
9 int number = 8;
10 cout << number << endl; // 8
11 cout << *squarePtr(number) << endl; // 64
12 cout << squareRef(number) << endl; // 64
13}
14
15int * squarePtr(int number) {
16 int * dynamicAllocatedResult = new int(number * number);
17 return dynamicAllocatedResult;
18}
19
20int & squareRef(int number) {
21 int * dynamicAllocatedResult = new int(number * number);
22 return *dynamicAllocatedResult;
23}
2.6 总结
指针和引用掌握起来都很复杂,但是它们能提高程序的效率。所以一定要深入理解,合理使用。
3. 动态内存分配
3.1 new 和 delete 操作符
除了定义一个int
型变量,然后将其地址赋值给int
指针,记忆体也可以在运行时通过new
操作符动态分配。
在 C++里,但凡只用new
动态分配的记忆体,都需要使用delete
来释放,也就是将内存返还给堆。
new
操作符返回指向分配的记忆体的指针,delete
操作符需要一个指针作为唯一参数。
1// Static allocation
2int number = 88;
3int * p1 = &number; // Assign a "valid" address into pointer
4
5// Dynamic Allocation
6int * p2; // Not initialize, points to somewhere which is invalid
7cout << p2 << endl; // Print address before allocation
8p2 = new int; // Dynamically allocate an int and assign its address to pointer
9 // The pointer gets a valid address with memory allocated
10*p2 = 99;
11cout << p2 << endl; // Print address after allocation
12cout << *p2 << endl; // Print value point-to
13delete p2; // Remove the dynamically allocated storage
注意到,new
和delete
都作用于指针。
初始化一个动态分配的记忆体,可以使用基本数据类型的初始化,也可以调用构造函数来初始化对象。
1// use an initializer to initialize a fundamental type (such as int, double)
2int * p1 = new int(88);
3double * p2 = new double(1.23);
4
5// C++11 brace initialization syntax
6int * p1 = new int {88};
7double * p2 = new double {1.23};
8
9// invoke a constructor to initialize an object (such as Date, Time)
10Date * date1 = new Date(1999, 1, 1);
11Time * time1 = new Time(12, 34, 56);
你也可以在函数内动态分配一个全局指针变量。动态分配的记忆体在函数调用后一直被占用,直到被释放。
1// Dynamically allocate global pointers (TestDynamicAllocation.cpp)
2#include <iostream>
3using namespace std;
4
5int * p1, * p2; // Global int pointers
6
7// This function allocates storage for the int*
8// which is available outside the function
9void allocate() {
10 p1 = new int; // Allocate memory, initial content unknown
11 *p1 = 88; // Assign value into location pointed to by pointer
12 p2 = new int(99); // Allocate and initialize
13}
14
15int main() {
16 allocate();
17 cout << *p1 << endl; // 88
18 cout << *p2 << endl; // 99
19 delete p1; // Deallocate
20 delete p2;
21 return 0;
22}
静态分配和动态分配的主要区别:
- 静态分配是编译器通过内存管理器自动完成的,而动态分配,是程序员自己操作的。对于动态分配的记忆体,程序员有对这段记忆体的全部控制权。
- 静态分配是通过变量名管理的,而动态分配是通过指针。
3.2 new[] 和 delete[] 操作符
动态数组是在运行时通过new[]
操作符动态分配的。相应的通过delete[]
操作符释放。
1/* Test dynamic allocation of array (TestDynamicArray.cpp) */
2#include <iostream>
3#include <cstdlib>
4using namespace std;
5
6int main() {
7 const int SIZE = 5;
8 int * pArray;
9
10 pArray = new int[SIZE]; // Allocate array via new[] operator
11
12 // Assign random numbers between 0 and 99
13 for (int i = 0; i < SIZE; ++i) {
14 *(pArray + i) = rand() % 100;
15 }
16 // Print array
17 for (int i = 0; i < SIZE; ++i) {
18 cout << *(pArray + i) << " ";
19 }
20 cout << endl;
21
22 delete[] pArray; // Deallocate array via delete[] operator
23 return 0;
24}
C++03 不允许初始化动态分配的数组,C++11 中可以用下面的方式对动态数组初始化:
1int *p = new int[5] {1, 2, 3, 4, 5};
4 指针,数组和函数
4.1 数组被视为指针
在 C/C++中,数组的名字就是指向数组第一个元素的地址的指针。例如有一个int
类型数组"numbers”,“numbers"就是&numbers[0]
,相应地,*numbers
是numbers[0]
, *(numbers + i)
是 numbers[i]
1/* Pointer and Array (TestPointerArray.cpp) */
2#include <iostream>
3using namespace std;
4
5int main() {
6 const int SIZE = 5;
7 int numbers[SIZE] = {11, 22, 44, 21, 41}; // An int array
8
9 // The array name numbers is an int pointer, pointing at the
10 // first item of the array, i.e., numbers = &numbers[0]
11 cout << &numbers[0] << endl; // Print address of first element (0x22fef8)
12 cout << numbers << endl; // Same as above (0x22fef8)
13 cout << *numbers << endl; // Same as numbers[0] (11)
14 cout << *(numbers + 1) << endl; // Same as numbers[1] (22)
15 cout << *(numbers + 4) << endl; // Same as numbers[4] (41)
16}
4.2 指针运算
正如上一节中提到的,如果’numbers’是一个int
型的数组,它可以被视为int
型的指针,指向这个数组的第一个元素。(numbers + 1)
指向下一个元素,需要注意的是,
int
通常占 4 字节,也就是说(numbers + 1)
会增加 4 个地址位置,或者sizeof(int)
个步长。
1int numbers[] = {11, 22, 33};
2int * iPtr = numbers;
3cout << iPtr << endl; // 0x22cd30
4cout << iPtr + 1 << endl; // 0x22cd34 (increase by 4 - sizeof int)
5cout << *iPtr << endl; // 11
6cout << *(iPtr + 1) << endl; // 22
7cout << *iPtr + 1 << endl; // 12
4.3 sizeof 数组
对数组执行sizeof
操作会返回该数组所占用的字节数。你可以通过数组总字节数除以单个元素字节数来计算数组长度。
1int numbers[100];
2cout << sizeof(numbers) << endl; // Size of entire array in bytes (400)
3cout << sizeof(numbers[0]) << endl; // Size of first element of the array in bytes (4)
4cout << "Array size is " << sizeof(numbers) / sizeof(numbers[0]) << endl; // (100)
4.4 数组作为函数参数或返回值
将数组传递给函数实际上传递的是数组第一个元素的指针。在申明函数的时候既可以使用数组形式,也可以使用指针形式,例如下面的申明是等效的:
1int max(int numbers[], int size);
2int max(int *numbers, int size);
3int max(int number[50], int size);
上面的申明中参数都会被编译器视为int *
,给定的数组大小会被忽略。
数组的大小不是数组参数的一部分,所以需要传递另一个整数参数来指定数组长度。编译器不能根据数组指针推断数组长度,也不会检查数组边界。
下面是使用数组申明的例子:
1/* Passing array in/out function (TestArrayPassing.cpp) */
2#include <iostream>
3using namespace std;
4
5// Function prototypes
6int max(const int arr[], int size);
7void replaceByMax(int arr[], int size);
8void print(const int arr[], int size);
9
10int main() {
11 const int SIZE = 4;
12 int numbers[SIZE] = {11, 22, 33, 22};
13 print(numbers, SIZE);
14 cout << max(numbers, SIZE) << endl;
15 replaceByMax(numbers, SIZE);
16 print(numbers, SIZE);
17}
18
19// Return the maximum value of the given array.
20// The array is declared const, and cannot be modified inside the function.
21int max(const int arr[], int size) {
22 int max = arr[0];
23 for (int i = 1; i < size; ++i) {
24 if (max < arr[i]) max = arr[i];
25 }
26 return max;
27}
28
29// Replace all elements of the given array by its maximum value
30// Array is passed by reference. Modify the caller's copy.
31void replaceByMax(int arr[], int size) {
32 int maxValue = max(arr, size);
33 for (int i = 0; i < size; ++i) {
34 arr[i] = maxValue;
35 }
36}
37
38// Print the array's content
39void print(const int arr[], int size) {
40 cout << "{";
41 for (int i = 0; i < size; ++i) {
42 cout << arr[i];
43 if (i < size - 1) cout << ",";
44 }
45 cout << "}" << endl;
46}
接着是使用指针申明的例子:
1/* Passing array in/out function using pointer (TestArrayPassingPointer.cpp) */
2#include <iostream>
3using namespace std;
4
5// Function prototype
6int max(const int *arr, int size);
7
8int main() {
9 const int SIZE = 5;
10 int numbers[SIZE] = {10, 20, 90, 76, 22};
11 cout << max(numbers, SIZE) << endl;
12}
13
14// Return the maximum value of the given array
15int max(const int *arr, int size) {
16 int max = *arr;
17 for (int i = 1; i < size; ++i) {
18 if (max < *(arr+i)) max = *(arr+i);
19 }
20 return max;
21}
4.5 Pass-by-Reference 和 sizeof
1/* Test sizeof array (TestSizeofArray.cpp) */
2#include <iostream>
3using namespace std;
4
5// Function prototypes
6void fun(const int *arr, int size);
7
8// Test Driver
9int main() {
10 const int SIZE = 5;
11 int a[SIZE] = {8, 4, 5, 3, 2};
12 cout << "sizeof in main() is " << sizeof(a) << endl;
13 cout << "address in main() is " << a << endl;
14 fun(a, SIZE);
15}
16
17// Function definitions
18void fun(const int *arr, int size) {
19 cout << "sizeof in function is " << sizeof(arr) << endl;
20 cout << "address in function is " << arr << endl;
21}
编译运行:
1→ g++ test_sizeof_array.cpp && ./a.out
2sizeof in main() is 20
3address in main() is 0x7ffeeab9d0d0
4sizeof in function is 8
5address in function is 0x7ffeeab9d0d0
看到这个结果是不是很不可思议,在main
函数和fun
中打印的地址相同,但是sizeof
的结果却不一样,这是为什么呢?
因为在main
中sizeof
是对数组操作的,而在fun
中,sizeof
操作的是指针,指针变量占的字节数是 4。
4.6 数组的遍历操作
1/* Function to compute the sum of a range of an array (SumArrayRange.cpp) */
2#include <iostream>
3using namespace std;
4
5// Function prototype
6int sum(const int *begin, const int *end);
7
8// Test Driver
9int main() {
10 int a[] = {8, 4, 5, 3, 2, 1, 4, 8};
11 cout << sum(a, a+8) << endl; // a[0] to a[7]
12 cout << sum(a+2, a+5) << endl; // a[2] to a[4]
13 cout << sum(&a[2], &a[5]) << endl; // a[2] to a[4]
14}
15
16// Function definition
17// Return the sum of the given array of the range from
18// begin to end, exclude end.
19int sum(const int *begin, const int *end) {
20 int sum = 0;
21 for (const int *p = begin; p != end; ++p) {
22 sum += *p;
23 }
24 return sum;
25}
4.7 C-String 和 指针
C-String (在 C 语言中)是一个以字符'\0'
结尾的字符数组:
1/* Testing C-string (TestCString.cpp) */
2#include <iostream>
3#include <cstring>
4using namespace std;
5
6int main() {
7 char msg1[] = "Hello";
8 char *msg2 = "Hello";
9 // warning: deprecated conversion from string constant to 'char*'
10
11 cout << strlen(msg1) << endl; // 5
12 cout << strlen(msg2) << endl;
13 cout << strlen("Hello") << endl;
14
15 int size = sizeof(msg1)/sizeof(char);
16 cout << size << endl; // 6 - including the terminating '\0'
17 for (int i = 0; msg1[i] != '\0'; ++i) {
18 cout << msg1[i];
19 }
20 cout << endl;
21
22 for (char *p = msg1; *p != '\0'; ++p) {
23 // *p != '\0' is the same as *p != 0, is the same as *p
24 cout << *p;
25 }
26 cout << endl;
27}
需要注意的是,像strlen()
这样的 C-String 函数,不需要传递数组长度。因为 C-String 以'\0'
结尾,这些函数可以
遍历字符数组直到遇到字符'\0'
.
1/* Function to count the occurrence of a char in a string (CountChar.cpp) */
2#include <iostream>
3#include <cstring>
4using namespace std;
5
6int count(const char *str, const char c); // No need to pass the array size
7
8int main() {
9 char msg1[] = "Hello, world";
10 char *msg2 = "Hello, world";
11
12 cout << count(msg1, 'l') << endl;
13 cout << count(msg2, 'l') << endl;
14 cout << count("Hello, world", 'l') << endl;
15}
16
17// Count the occurrence of c in str
18// No need to pass the size of char[] as C-string is terminated with '\0'
19int count(const char *str, const char c) {
20 int count = 0;
21 while (*str) { // same as (*str != '\0')
22 if (*str == c) ++count;
23 ++str;
24 }
25 return count;
26}
5 指针的其他方面
5.1 函数指针
在 C/C++中,函数跟其他数据一样,都有自己的地址。函数名就是函数在记忆体中的起始地址,因此函数名可以被视为指针。 我们可以给函数传递一个函数指针,语法如下:
1// Function-pointer declaration
2return-type (* function-ptr-name) (parameter-list)
3
4// Example
5double (*fp)(int, int) // fp points to a function that takes two ints and returns a double (function-pointer)
6double *dp; // dp points to a double (double-pointer)
7double *fun(int, int) // fun is a function that takes two ints and returns a double-pointer
8
9double f(int, int); // f is a function that takes two ints and returns a double
10fp = f; // Assign function f to fp function-pointer
1/* Test Function Pointers (TestFunctionPointer.cpp) */
2#include <iostream>
3using namespace std;
4
5int arithmetic(int, int, int (*)(int, int));
6 // Take 3 arguments, 2 int's and a function pointer
7 // int (*)(int, int), which takes two int's and return an int
8int add(int, int);
9int sub(int, int);
10
11int add(int n1, int n2) { return n1 + n2; }
12int sub(int n1, int n2) { return n1 - n2; }
13
14int arithmetic(int n1, int n2, int (*operation) (int, int)) {
15 return (*operation)(n1, n2);
16}
17
18int main() {
19 int number1 = 5, number2 = 6;
20
21 // add
22 cout << arithmetic(number1, number2, add) << endl;
23 // subtract
24 cout << arithmetic(number1, number2, sub) << endl;
25}
5.2 通用指针或者 void 指针(void \*)
void
指针可以用来存放任何数据类型的地址(除了函数指针)。我们不能对void
指针执行操作,因为它的类型是未知的。
但是我们可以使用void
指针来跟其他地址做比较。
5.3 常数的指针 vs. 指针是常数
非常数指针指向常数:指针指向的数据不能修改,但是指针本身可以修改为指向其他数据,例如:
1int i1 = 8, i2 = 9;
2const int *iptr = &i1; // non-constant pointer pointing to constant data
3// *iptr = 9; // error: assignment of read-only location
4iptr = &i2; // ok
常数指针指向非常量数据:指向的数据可以被修改,但是指针本身不能修改:
1int i1 = 8, i2 = 9;
2int * const iptr = &i1;
3*iptr = 9; // ok
4// iptr = &i2; // error: assignment of read-only variable
常数指针指向常量:指向的数据和指针本身都不能修改:
1int i1 = 8, i2 = 9;
2const int * const iptr = &i1; // constant pointer pointing to constant data
3// *iptr = 9; // error: assignment of read-only variable
4// iptr = &i2; // error: assignment of read-only variable
非常数指针指向非常数:指向的数据和指针都能被修改
评论