第四章数组和指针
Arrays and Points
vector和array类似的地方就是它们都是同一对象类型的集合。不同点是array是固定长度的。iterator之于vector就如同指针之于array。
使用指针的一个重要原因是vector不能提供所需的访问速度。
数组类型不能是引用(reference)
4.1 Array
关于Array初始化:
l 使用class默认的构造函数初始化每个单元。
l Character Array即可以用字符数组来初始化也可以用string来初始化,注意如果用string,那么自动在末尾追加了null作为结束符,这样数组的长度+1.
下标操作时,要使用数据类型是:size_t,就像在vector中使用size_type。
for (size_t ix = 0; ix != array_size; ++ix)
ia[ix] = ix;
|
4.2 Pointers
什么是指针
Pointers are iterators for arrays.指针是用于数组的迭代器。
pointer holds the address of another object:。具体来说,指针保存的是另一个对象的地址。
string *sp = &s; // sp holds the address of s
|
*sp里面*代表sp是一个指针。
Best practice:指针初始化时,如果不能指向具体的地址,就设置为0,这样在程序中就可以检测出指针没有指向一个对象(object)。
指针初始化和赋值操作的约束
指针初始化和赋值
只有4类值可以初始化指针和指针赋值:
1.值为0的常量表达式。不能把任何的int赋值给指针,即使这个int的值是0,但是可以把值是0的const 或者数值0赋值给指针。NULL定义为0,所以也可以使用。NULL叫做预处理变量。
2.类型匹配的对象的地址
3.另一对象末的下一地址
4.同类型的其它有效的指针
void*指针,这是一个特殊类型的指针,它能够是任何对象的地址。它就是说明它的值是一个指针,但是指针所指向的对象的类型是不可知的。
因此void*指针只能执行通用的操作:
l 指针比较
l 传递给function或者作为function的返回值。
l 赋值给另一个void*指针
Operations on Pointer(指针操作)
注意*在这里是一个operator。dereference a pointer。解引用操作(dereference operator)返回指定对象的左值lvalue。因此可以进行赋值操作。
引用和指针的区别:
1. 引用总是指向对象object。引用必须初始化,但指针是可以仅仅定义,而无需初始化的。
2. 赋值:引用的赋值会改变引用所绑定的对象的值;引用是不能重新绑定新的对象的。一旦初始化,引用总是指向同一个对象。
引用:
int &ri = ival, &ri2 = ival2;
ri = ri2; // assigns ival2 to ival这里ri和ri2还是指向2个不同的地址单元
|
指针:
int ival = 1024, ival2 = 2048;
int *pi = &ival, *pi2 = &ival2;
pi = pi2; // pi now points to ival2,pi和pi2都指向了同一个地址单元
|
指向指针的指针**ppi,代码:
int ival=1024;
int *pi = &ival;
int **ppi=π
int *pi2 =*ppi; //或者 int *pi2 = pi;
|
对ppi解引用2次,就可以得到ival的值了。
1. 使用指针访问数组单元
当使用数组的名字时,这个名字就自动转换为指向数组第一个单元的指针。
int ia[] = {0,2,4,6,8};
int *ip = ia; // ip points to ia[0] 定义ip是指针,赋值为ia
|
指针的算数运算会产生一个新指针。
两指针相减得到的数据类型是ptrdiff_t。它是一个
int last = *(ia + 4); // ok: initializes last to 8, the value of ia[4]
|
如何获得一个数组的结尾?
const size_t arr_size = 5;
int arr[arr_size] = {1,2,3,4,5};
int *p = arr; // ok: p points to arr[0]
int *p2 = p + arr_size; // ok: p2 points one past the end of arr
|
p2就是指向了数组的结尾。
指向const对象的指针和const指针
1. 指向const对象的指针
如果指针指向的是一个const对象,那么肯定不希望它能够修改这个const对象的值。那么这样的指针如何定义?
const double *cptr; //cptr是指向const double类型的指针
|
对于这个定义的理解是这样子的:
A. 这里的限定词(qualifier)-const是限定cptr指向的数据类型必须是const double,而不是限定cptr本身。
B. cptr本身不是const
C. 不能做的是用cptr去修改它指向的对象的值
但是也可以把指向const对象的指针赋值为非const对象的地址。(A pointer to a const object can be assigned the address of a nonconst object)
int val=10;
const int *cptr = &val;
val=30; //这样写是对的
*cptr = 30; //这样写会死人的L
|
不过我想,这只不过是代码上的文字游戏而已,如果程序这样写,也许会死人的。混乱!
这种指针的用途是作为方法的参数,这可以保证这个参数不会在方法中被修改。(哎,要不怎么说是大师呢,佩服!)
2. const指针
const指针的值是不可以修改的,但是const指针指向的对象的值是可以修改的。
int errNumb = 0;
int *const curErr = &errNumb; // curErr is a constant pointer
*curErr = 0; // ok: reset value of the object to which curErr is bound
|
3. 指针和typedef
复习:
typedef 可以用来定义类型的同义词。wages就是double的同义词
typedef double wages; // wages is a synonym for double
|
(以下的这段很饶人啊)
typedef string *pstring; //pstring是string的同义词;并且pstring是指针类型
const pstring cstr; //这是指向string的const指针,而不是指向const string的指针
|
第一绕:pstring类型是指向string类型的指针类型。
第二绕:const是用来修饰pstring类型的,const pstring就是const的string指针
第三绕:这不存在简单的文字替换的游戏:const pstring cstr;等于const string* cstr;所以就是指向const string的指针。错误!
4.3 C风格字符串
不建议在C++中使用C风格的字符串,原因是它有“many many”的安全问题。
1. 什么是C风格的字符串?
它不是一种类型,而是以空字符 null 结束的字符数组。
定义:
char ca2[] = {'C', '+', '+', '"0'}; // explicit null
char ca3[] = "C++"; // null terminator added automatically
const char *cp = "C++"; // null terminator added automatically
char *cp2 = ca2; // points to first element of a null-terminated char array
|
遍历字符串
const char *cp = "some value";
while (*cp) {
// do something to *cp
++cp;
}
|
2. 字符串函数
C标准库提供了一系列的字符串函数,但是这些函数都不检查参数的合法性(限制条件)。这就是Lippman不建议在C++中使用C风格的字符串的原因。
主要的限制:
1. 传递给这些标准库函数的指针必须是非空的。
2. 每个指针必须是以null结尾的。
3. 数组的长度要足够大
strlen(s),返回的是s的长度,但是这里并不包含null,因此数组的实际长度是strlen(s)+1。
3. 使用动态数组
为什么要使用动态数组?
通常是因为在编译时无法知道数组的维数,所以才需要动态创建该数组。
size_t n = get_size(); // get_size returns number of elements needed
int* p = new int[n];
|
如果是普通的定义数组,以上的定义是根本无法实现的,因为在编译期是不能确定数组的大小的。只能这样定义:
因此就可以得出结论:动态数组主要是用于当数组的尺寸不能在编译期确定的情况。
数组释放:
4. 疑问代码(P139):
我认为在const size_t len = strlen(pc +1); 这行存在笔误,应该是
const size_t len = strlen(pc )+1;
否则copy后得到的结果pc2不是C风格的字符串,因为最后一个单元的内容是’g’而不是null。
const char *pc = "a very long literal string";
const size_t len = strlen(pc +1); // space to
// performance test on string allocation and copy
for (size_t ix = 0; ix != 1; ++ix) {
char *pc2 = new char[len + 1]; // allocate the space
strcpy(pc2, pc); // do the copy
cout << "pc2=" << pc2 << endl;
cout << "pc2[25]=" << (pc2+25) << endl;
delete [] pc2; // free the memory
}
|
4.4多维数组 Multidimensioned Arrays
C++实际是没有多维数组的,
int ia[3][4]
可以理解为数字大小是3,每个单元element又是一个大小是4的int数组。或者叫做3行3列。
1. 指针和多维数组
这段代码就如同“回”字的N多种写法一样:
int ia[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
//*ip是一个指针变量,它指向的是长度是4的int数组。
int (*ip)[4]=ia;
//*ipo是一个指针数组
int *ipo[4] ;
ipo[2]=ia[2]; //对指针数组的单元赋值
//*ipt就是一个最普通的指针,对指针变量赋值
int *ipt=ia[2];
//
int (*ipn)[4]=ia;
ipn=&ia[2]; //也可以写为ia+2
cout << "(*(ip+2))[0]=" << (*(ip+2))[0] << endl;
cout << "ipo[2][0]=" << ipo[2][0] << endl;
cout << "ipt[0]=" << ipt[0] << endl;
cout << "(*ipn)[0]=" << (*ipn)[0] << endl;
|
打印结果是一样的,都是‘8’。
2. 以下2种定义的区别(圆括号是必不可少的)
指针数组:
int *ip[4]; // array of pointers to int
|
如果从内向外阅读 ip 的声明,则可理解为:*ip 是 int[4] 类型——即 ip 是一个指向含有 4 个元素的数组的指针。
int (*ip)[4]; // pointer to an array of 4 ints
|
进一步的理解:
int (*ip)[4]=ia;
ip=&ia[2]; //也可以写为ia+2
|
刚开始,我觉得应该写成ip=ia[2];结果得到报错信息:
error: cannot convert `int[4]' to `int (*)[4]' in assignment
|
因为ia[2]就是一个数组啊,再想如果这样写ip=ia[2];这就相当于
int ia[4]= {0,1,2,3};
int *ip=ia;
ip=ia[2];
|
当然是错误的。