指针(含部分数组)

数组和指针

指针里面存了一个对象的地址,与迭代器类似也可以进行解引用操作。对指针进行解引用操作可以访问它所指向的对象。

指针的定义和初始化

理解指针的声明语句时,从右向左阅读。例如

1
string *pstring;

语句把 pstring 定义为一个指向 string 类型对象的指针变量。

在声明语句中,* 可以用在指定类型的对象列表的任何位置。

1
double dp, *dp2;

定义了一个 double 类型的 dp 对象和一个指向 double 类型对象的指针 dp2

另一种指针声明风格

1
string* ps;

这样容易把 string** 误认为一种数据类型,认为在同语句中定义的其他变量也是指向 string* 类型的指针,例如

1
string* ps1, ps2;

实际上只有 ps1 是指针变量,ps2 并非指针。这样容易造成混淆,所以如果真的喜欢第二种的话请尽量分开声明。

注:未初始化的指针是无效的(但是语法允许),初始化为0值的指针代表的是它不指向任何对象。区别于引用,没有初始化的引用是错误

注2[最佳实践]:如果可能的话,除非所指向的对象已经存在,否则不要先定义指针,这样可以避免定义一个未初始化的指针;如果必须分开定义的话,一定要将指针初始化为0。

int 型变量赋给指针是非法的,即使该 int 是 0。但是可以把数字 0 或在编译时可获得 0 值得 const 量赋给指针。如下:

1
2
3
4
5
6
7
8
int zero = 0;
const int c_zero = 0;

int *pointer;
pointer = zero; //不行
pointer = c_zero; //行
pointer = 0; //行
pointer = NULL; //行,NULL是<cstdlib>里面定义的值为0的常量

void *指针

void* 表明该指针与一地址值相关,但不清楚指向的对象类型。不允许使用 void* 来操纵它所指向的对象。

指针的算数操作

只要两个指针指向同一数组或有一个指向该数组末端的下一单元,可以对着两个指针进行做差

1
ptrdiff_t n = ip2 - ip;

两个指针做差的结果是 标准库类型 ptrdiff_t 数据。这是一个 signed 整型。因为两个指针的距离可以是负数。

下标和指针

在使用下标访问数组时,实际上是对指向数组元素的指针做下标操作。只要指针指向数组元素,就能操作

1
2
3
4
int *p = &ia[2]; //p指向ia数组的第二个元素
int j = p[1]; //等价于*(p + 1),也就是ia[3]

int k = p[-2]; //等价于ia[0]

注:C++允许计算数组或对象的超出末端地址,但是 不能 对其解引用。计算超出末端地址或者数组首地址之前的地址都是不合法的!

指针和 const 限定符

指针和 const 可以排列出两种类型:const double *cptr 和 double *const cptr。前者是“(自以为)指向 const 的指针”,而后者是“const 指针”。

自以为)指向 const 的指针指向的值可以不是 const 对象,但是不能通过这个指针来修改对象的值,但是可以修改指针指向的对象。此时指针指向的是 const double 类型。也叫底层const

const 指针 指向任何对象,只是不能改变它指向的对象(指针本身是const)。但是如果该指针指向的对象不是 const 类型,那么可以通过该指针修改对象的值,但是不能修改这个指针指向的对象。也叫顶层const

也可以将二者结合起来,形成一个既不能改变指向对象也不能改变对象值的指针,即 const double *const pptr = \π

注:const int ci = 42; 其中的 ci 也是顶层 const,用于声明引用的 const 都是底层 const。如 const int &r = ci;

指针和 typedef

如下代码经常容易搞错

1
2
typedef string *pstring;
const pstring cstr;

cstr 来说,它的数据类型是 const 指针 ,也就是 string \const cstr*。此时的基本数据类型是指针

所以在用 typedefconst 类型定义时,最好将 const 放在类型后面

C风格字符串

尽量少用,能用 string 就用 string

在前面学习的过程中经常使用 <string.h> 中的 strcat()strcpy() 方法,但是这两个函数由于没有字符复制的限制,所以很容易造成漏洞。所以要尽量采用安全的字符串拼接和复制方法。

1
2
3
#include <cstring>
strncat(s1, s2, n); //将s2的前n个字符连接到s1后面,并返回s1
strncpy(s1, s2, n); //将s2的前n个字符赋值给s1,并返回s1

注:每次执行复制或者拼接操作时,最后一个参数一定要记得算上null结束符!!

注2:使用 strlen() 函数带来的返回值里面 不包含 结束符null。

创建动态数组

通过 newdelete 分配的变量在堆上。有两种办法

1
2
int *pia = new int[10]; //创建了一个10个元素的没有被初始化的数组
int *pia2 = new int[10](); //创建了一个10个元素的,被初始化成0的数组

尤其是当数组为 const 数组时,两种语句的差距才体现出来。

1
2
const int *pci_bad = new const int[100]; //不行,因为const数组的元素没有被初始化
const int *pci_ok = new const int[100](); //行,const数组的元素被初始化了

回收动态数组的空间时,使用 delete [ ] pointer 的语句。其中 [ ] 一定不能漏掉。否则编译器不报错但是运行时也要报错。

新旧代码的兼容

string 类中提供了一个 c_str() 方法来将 string 类的字符串转换成 *const char ** 的格式。如

1
2
string st2("something");
const char *str = st2.c_str(); //注意前面必须是const

使用数组初始化 vector 对象

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

1
2
3
const size_t arr_size = 6;
int int_arr[arr_size] = {0, 1, 2, 3, 4 ,5};
vector<int> ivec(int_arr, int_arr + arr_size);

注:此处的“第一个元素”并不代表数组的首元素!

空指针

C++11中使用nullptr来初始化空指针,同时避免使用NULL(包含在中)