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