数组
定义及其初始化
1、数组的维数必须用值大于等于1的常量表达式定义(包括:整型字面值常量、枚举常量、用常量表达式初始化的整型const对象),非const变量以及到运行阶段才知道其值的const变量都不能用于定义数组的维数。
3、定义数组时没有显式的初始化,则:
----在函数体外定义的内置数组,元素均初始化为0
----在函数体内定义的内置数组,元素均无初始化
----无论在何处定义,若其元素为类类型,则自动调用其默认的构造函数初始化;若无构造函数,则必须为该数组的元素提供显式的初始化
2、字符数组可以使用字符串字面值进行初始化,但当使用字符串字面值来初始化字符数组时,将在数组后面加入空字符,例如:char A[] = "C++"; //A的维数是4
3、与vector不同,一个数组不能用另一个数组初始化,也不能将一个数组赋值给另一个数组
注意:数组长度固定,一旦定义,就无法添加新的元素。但是若要添加,则需要自己更改内存remalloc()以及malloc()函数可以实现
数组的操作
指针
定义及其初始化
建议:尽量避免使用指针和数组
指针和数组容易产生不可预料的错误。其中一部分是概念上的问题:指针用于低级操作,容易然生与繁琐细节相关的(book keeping)错误。其他错误则源于使用指针的语法规则,特别是声明指针的语法。
许多有用的程序都可不使用数组或指针实现,现代C++程序采用vector类型和迭代器取代一般的数组、采用string类型取代C风格字符串。
指针可能的取值
一个有效的指针必然是以下三种状态之一:保存一个特定对象的地址;指向某个对象后面的另一对象;或者是0值。若指针保存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
指针初始化和赋值操作的约束
对指针进行初始化或赋值只能使用以下四种类型的值:
(1)0值常量表达式。
(2)类型匹配的对象的地址。
(3)另一对象之后的下一地址。
(4)同类型的另一个有效指针。
把int型变量赋给指针是非法的,尽管此int型变量的值可能为0。
void*指针
C++提供了一种特殊的指针类型void*,它可以保存任何类型对象的地址:
- <pre name="code" class="cpp"> 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</pre>
----void*表明该指针与一地址值相关,但不清楚存储在此地址上的对象的类型。
----void*指针只支持几种有限的操作:与另一个指针进行比较;向函数传递void*指针或从函数返回void*指针;给另一个void*指针复制。
----不允许用void*指针操纵它所指向的对象。
指针操作以及指针有关注意事项
一、解引用操作生成左值
二、关键概念:给指针赋值或通过指针进行赋值
对于初学指针者,给指针赋值和通过指针进行赋值这两种操作的差别确实让人费解。谨记区分的重要方法是:如果对左操作数进行解引用,则修改的是指针所指向的值;如果没有使用解引用操作,则修改的是指针本身的值。
三、指针和引用的比较
第一个区别在于引用总是指向某个对象:定义引用时没有初始化是错误的。第二个重要区别则是复制行为的差异:给引用赋值修改的是该引用所关联的对象的值,而并不是使引用与另一个对象关联。引用一经初始化,就始终指向同一个特定对象(这就是为什么引用必须在定义时初始化的原因)。
四、指向指针的指针
指针本身也是可用指针指向的内存对象。指针占用内存空间存放其值,因此指针的存储地址可存放在指针中。
- int ia[] = {0, 2, 4, 6, 8};
- int *ip =ia; // ip points to ia[0]
- ip = &ia[4]; // ip points to last element in ia
- ip = ia; // ok: ip points to ia[0]
- int *ip2 = ip + 4; // ok: ip2 points to ia[4], the last element in ia
指针的算数操作只有在原指针和计算出来的新指针都指向同一个数组的元素,或指向该数组存储空间的下一单元时才是合法的。如果指针指向一对象,我们还可以在指针上加1从而获取指向相邻的下一个对象的指针。
五、C++还支持对这两个指针做减法操作:
- ptrdiff_t n = ip2 - ip; // ok: distance between the pointers
结果是4,这两个指针所指向的元素间隔为4个对象。两个指针减法操作的结果是标准库类型ptrdiff_t的数据。与size_t类型一样,ptrdiff_t也是一种与机器相关的类型,在cstddef头文件中定义。size_t是unsigned类型,而ptrdiff_t则是signed_t整型。
允许在指针上加减0,使指针保持不变。如果一指针具有0值,则在该指针上加0仍然是合法的,结果得到另一个值为0的指针。也可以对两个空指针做减法操作,得到的结果仍是0。
六、解引用和指针算术操作之间的相互作用
在指针上加一个整型数值,其结果仍然是指针。允许在这个结果上直接进行解引用操作,而不必先把它赋给一个新指针:
- int last = *(ia + 4); // ok: initializes last to 8, the value of ia[4]
加法操作两边用圆括号括起来是必要的。如果写为:
- last = *ia + 4;// ok: last = 4, equivalent to ia[0]+4
意味着对ia进行解引用,获得ia所指元素的值ia[0],然后加4。
七、计算数组的超出末端指针
- <span style="font-size:14px"> 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
- // use caution -- do not dereference!</span>
C++允许计算数组或对象的超出末端的地址,但不允许对此地址进行解引用操作。而计算数组超出末端位置之后或数组首地址之前的地址都是不合法的。
可使用此超出末端指针的当做一个哨兵,如同在vector中使用的end变量一般,用于输出和遍历数组,这是一个好习惯
八、指针和const限定符
指向const对象的指针
- const double *cptr; // cptr may point to a double that is const
const限定了cptr指针所指向的对象类型,而并非cptr本身。也就是说,cptr本身并不是const。
允许通过给cptr赋值,使其指向一个const对象,但不能通过cptr修改其所指向的对象的值。
不能使用void*指针保存const对象的地址,而必须使用const void*类型的指针保存const对象的地址:
- const int universe = 42;
- const void *cpv = &universe; // ok: cpv is const
- void *pv = &universe; // error: universe is const
不能使用指向const对象的指针修改基础对象,然而如果该指针指向的是一个非const对象,可用其他方法修改其所指的对象。
九、const指针
C++语言还提供了const指针——本身的值不能修改:
- int errNumb = 0;
- int *const curErr = &errNumb; // curErr is a constant pointer
- curErr = curErr; // error: curErr is a constant pointer
指向const对象的const指针,既不能修改所指对象的值,也不允许修改指针的指向。
假设给出以下语句:
- typedef string *pstring;
- const pstring cstr;
- <span style="font-size:12px"> const string *pstring; // wrong interpretation of const pstring cstr
- </span><span style="font-size:14px; color:#ff0000"> string *const cstr; // equivalent to const pstring cstr,等价于这句话</span>
C风格字符串
C风格字符串的标准库函数(要使用这些标准库函数,必须包含相应的C头文件:cstring)
strlen(s) strcmp(s1, s2) strcat(s1, s2)
strcpy(s1, s2) strncat(s1, s2, n) strncpy(s1, s2, n)
注意:这些标准库函数不会检查其字符串参数
永远不要忘记字符串结束符null,调用者必须确保目标字符串具有足够的大小
如果必须使用C风格字符串,则使用标准库函数strncat和strncpy比strcat和strcpy函数更安全
- char largeStr[16 + 18 + 2]; // to hold cp1 a space and cp2
- strncpy(largeStr, cp1, 17); // size to copy includes the null
- strncat(largeStr, " ", 2); // pedantic, but a good habit
- strncat(largeStr, cp2, 19); // adds at most 18 characters, plus a null
对大部分的应用而言,使用标准库类型string,除了增强安全性外,效率也提高了,因此应该尽量避免使用C风格字符串。
创建动态数组
动态数组的定义
- int *pia = new int[10]; // array of 10 uninitialized ints
new表达式返回指向新分配数组的第一个元素的指针
初始化动态分配的数组
可使用跟在数组长度后面的一对空圆括号,对数组元素做值初始化:
- int *pia2 = new int[10](); // array of 10 uninitialized ints
对于动态分配的数组,其元素只能初始化为元素类型的默认值,而不能像数组变量一样,用初始化列表为数组元素提供各不相同的初值。
const对象的态数组
- // error: uninitialized const array
- const int *pci_bad = new const int[100];
- // ok: value-initialized const array
- const string *pci_ok = new const int[100]();
允许动态分配空数组
- 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值。
动态空间的释放
动态分配的内存最后必须进行释放,否则,内存最终将会逐渐耗尽。
使用数组初始化vector对象
- <strong> </strong> 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<int> ivcec(int_arr, int_arr + arr_size);
多维数组
严格地说, C++ 中没有多维数组,通常所指的多维数组其实就是数组的数组。
- <pre name="code" class="cpp">#include <stdio.h>
- main()
- {
- static int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}}; /* 定义一个3行4列的二维数组 */
- int *p;
- printf ("%d %d/n",a,*a);
- printf ("%d %d/n",a[0],*(a+0));
- printf ("%d %d/n",&a[0],&a[0][0]);
- printf ("%d %d/n",a[0][0],*(*(a+0)+0));
- for (p=a[0];p<a[0]+12;p++) /* 把a[0]的地址赋予指针变量p,条件判断p<a[0]+12表示的是小于最后一个元素的地址; */
- {
- if ((p-a[0])%4==0) /* 利用整数指针变量p减去当前地址判断出是不是已经显示出了四个值,换行回车 */
- {
- printf ("/n");
- }
- printf ("%4d",*p); /* 打印出元素的值 */
- }
- printf ("/n");
- }
- /*
- 第 5行中的a和*a打印出来的值,就会让人弄不明白我们知道数组传递的地址那么a表示这个数组的其实地址为什么*a却不是实际值呢?原因是在多维数组中 a+0表示的是第0行的首地址,a+1表示是第一行的首地址,那么*a其实就是*(a+0),那么第一个元素其实是a[0][0]
- 而*(a+0) 仅仅是把一个3行4列的二维数组拆为了三个一维数组,*(a+0)显然表示的不是物理位置也就不可能得到第一个元素的值了,它仅仅是一个内存地址也就是第 0行的启始地址,再看8行中的*(*(a+0)+0),它表示的则是第0行第0列个元素的值也就是a[0][0],再次强调*(a+0)表示的是数组第一行的首地址,也就是第二行的*(a+1),而元素值要得到显然就是*(*(a+0)+0)了如果是第0行第1个也就是*(*(a+0)+1)。
- */ </pre>