数组和指针:
容器和迭代器-----[类比]---à 数组和指针。
推荐使用容器和迭代器以避免犯错。数组和指针容易出错。
数组的定义:
数组的维数必须用大于等于1的常量表达式定义。常量表达式包括整型字面值常量,枚举常量或者用常量表达式初始化的整型const对象。
数组的显示初始化:
把初值用逗号分隔起来,并且用花括号括起来。
const unsigned array_size=3;
int ia[array_size]={0,1,2};//显示初始化也可不写出维度int ia[]={1,2,3};也可
若数组未经显示初始化。那么
在函数体外定义的内置数组,其元素均初始化为0;
在函数体内定义的内置数组,其元素无初始化;
不管函数在哪里定义,如果元素为类类型,则自动调用该类的默认构造函数进行初始化;若该类没有默认构造函数,则必须为该数组元素提供显示初始化。
特殊的字符数组:
有两种初始化方式
1 char chArray1={“c”,”+”,”+”}//末尾没有被加上空字符
2 char chArray2=”c++”//自动在末尾加上空字符
相应地,chArray1的维数是3,而chArray的维数为4。
因此,使用一组字符串字面值初始化字符数组时,一定要记得添加结束字符串的空字符。
const char chArray3[4]=”fine”//错,应该为chArray[5]=”fine”
数组操作:
vector使用vector::size_tyep作为下标的类型。
数组使用size_t作为下标的类型。使用时要注意保证下标值在正确范围之内。
指针:
指针用于指向对象,并且可以指向单个对象,而不是像迭代器那样只能用于容器内的元素。
指针定义和初始化:
为避免误会,推荐使用char *p风格而不是char* p的风格,推荐定义指针时就初始化。
指针进行初始化或者赋值只能使用一下的四种类型的值:
1 0值常量表达式
2 类型匹配的对象的地址
3 另一对象之后的下一地址
4 同类型的另一个有效指针
Void指针:
Void指针void*可以保存任何类型对象的地址。
指针的解引用操作符*:
string s(hellow world);
string *ptr=&s;
*prt=”hello”;
与引用的区别:
1引用在定义时必须初始化,而指针不需要
2引用已经初始化,就只想同一个对象(它本身),指针可以改变所指的对象。
指向指针的指针**:
int ival=1024;
int *pi=&ival;
int **ppi-π
用指针访问数组:
数组名是一个常量指针,指向它的第一个元素
用数组下标访问数组其实相当于用指针访问数组:
int ia[]={0,2,4,6,8};
int i=ia[0];
int *p=&ia[2];
int j=p[1]//等价于*(p+1),值为a[3]
int k=p[-2]//等价于*(p-2),值为a[0]
输出数组元素:
const size_t array_sz=5;
int int_arr[array_sz]={0,1,2,3,5};
for(int *pbegin=int_arr, *pend=int_arr+arr_sz;pbegin!=pend;++pbegin)
cout<<*pbegin<<” ”;
注意1:for语句中进行初始化时,如果定义的多个变量具有相同的类型,那么它们可以在for循环的初始化语句中同时定义它们。
注意2:C++允许计算数组或对象的超出末端的地址。如果vector允许迭代器指向其末端来实现其哨兵的作用.*pend初始化后,实际指向的是该数组超出末端的位置。
指针和const限定符:
const double ptr 指针本身不是const,指针指向的对象是double类型的const。
因此ptr可以指向其它的对象,但是不能改变所指向的对象的值。
使用指针指向const对象时,只能使用指向const对象的指针,否则普通指针将可能修改const对象的值,这与const对象”本身的值不能被修改”的特性矛盾。
也允许把非const对象的地址赋给指向const对象的指针,此时,尽管对象本身非const,但是由于指针是指向const对象的,因此也不能用这个指针修改这个对象的值。但是可以用普通指针修改这个对象的值。
总结:不能保证指向const对象的指针所指向的对象的值一定不可修改。
const指针:
指针本身的值不可修改。类比其它const常量,const指针需要再定义时初始化。
Int errNumb=0;
Int *const curErr=& errNumb;
尽管指针本身的值不可修改,但是指针所指对象的值可能可以修改。指针所值对象的值能否修改完全取决于该对象的类型。
指向cosnt对象的const指针:
指针本身不能改,其指向的对象也不可以改。
C风格字符串:
C风格的字符串是以NULL结束的字符数组。如:
char ca1[]={‘C’,’+’,’+’,’/0’};//显示第增加NULL
char ca2[]=”C++”;//自动增加NULL
const char *ca3=”C++”;// 自动增加NULL
同(const)char*类型的指针来操纵C风格字符串。
const char *p=”a value”;
while(*cp)
{
cout<<*cp<<endl;
++cp;
}
string.h是其标准版本,对应的C++版本是 cstring。其中含有操作C风格字符串的标准库函数,strlen(s),strcmp(s1,s2),strcat(s1,s2),strcpy(s1,s2),strncat(s1,s2,n),strncpy(s1,s2,n)
使用时切记C风格字符串结束符NULL。
关于字串的长度
字符串S=a0a1…an-1的长度为n,规定字符串的长度值为n。实际上,串结束符
‘/0’虽然客观上是存在的,但是并没有被计入字符串的长度。
极端情况下,空串(只有串结束符的串)的长度为零。
C风格字符串的标准库函数分析:
const char *cp1 = "A string example";
const char *cp2 = "A different string";
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
使用标准库函数 strncat 和 strncpy 的诀窍在于可以适当地控制复制字符的个数。特别是在复制和串连字符串时,一定要时刻记住算上结束符 null。在定义字符串时要切记预留存放 null字符的空间,因为每次调用标准库函数后都必须以此结束字符串 largeStr。让我们详细分析一下这些标准库函数的调用:
1调用 strncpy 时,要求复制 17 个字符:字符串 cp1 中所有字符,加上结束符 null。留下存储结束符 null 的空间是必要的,这样largeStr 才可以正确地结束。调用 strncpy 后,字符串 largeStr 的长度 strlen 值是 16。记住:标准库函数 strlen 用于计算 C 风格字符串中的字符个数,不包括 null结束符。
2调用 strncat 时,要求复制 2 个字符:一个空格和结束该字符串字面值的 null。调用结束后,字符串 largeStr 的长度是 17,原来用于结束 largeStr 的 null 被新添加的空格覆盖了,然后在空格后面写入新的结束符 null。
3第二次调用 strncat 串接 cp2 时,要求复制 cp2 中所有字符,包括字符串结束符 null。调用结束后,字符串 largeStr 的长度是35:cp1 的 16 个字符和 cp2 的 18 个字符,再加上分隔这两个字符串的一个空格。
4整个过程中,存储 largeStr 的数组大小始终保持为 36(包括结束符)。
创建动态数组:
每一个程序在执行时都占用一块可用的内存空间,用于存放动态分配的对象,此内存空间称为程序的自由存储区或堆。C 语言使用一对标准库函数 malloc 和 free 在自由存储区中分配存储空间,而 C++ 语言则使用 new 和 delete 表达式实现相同的功能。
int *pia = new int[10]; // array of 10 uninitialized ints
此 new 表达式分配了一个含有 10 个 int 型元素的数组,并返回指向该数组第一个元素的指针,此返回值初始化了指针 pia。
动态分配数组时,如果数组元素具有类类型,将使用该类的默认构造函数实现初始化;如果数组元素是内置类型,则无初始化:
string *psa = new string[10]; // array of 10 empty strings
int *pia = new int[10]; // array of 10 uninitialized ints
也可使用跟在数组长度后面的一对空圆括号,对数组元素做值初始化
int *pia2 = new int[10] (); // array of 10 uninitialized ints
允许动态分配空数组:
之所以要动态分配数组,往往是由于编译时并不知道数组的长度。我们可以编写如下代码
size_t n = get_size();
int* p = new int[n];
for (int* q = p; q != p + n; ++q)
/* process the array */ ;
计算数组长度,然后创建和处理该数组。
如果get_size()返回零,以上代码依然可以执行。
C++ 虽然不允许定义长度为 0 的数组变量,但明确指出,调用 new 动态创建长度为 0 的数组是合法的:
char arr[0]; // error
char *cp = new char[0]; // ok: but cp can't be dereferenced
使用了动态空间后,要释放,否则发生内存泄露。
Delete [] p;//方括号告诉编译器该指针指向的是自由存储区中的数组,而并非单个对象。
多维数组:
严格地说,C++ 中没有多维数组,通常所指的多维数组其实就是数组的数组:
和处理一维数组一样,程序员可以使用由花括号括起来的初始化式列表来初始化多维数组的元素。对于多维数组的每一行,可以再用花括号指定其元素的初始化式:
int ia[3][4] = { /* 3 elements, each element is an array of size 4 */
{0, 1, 2, 3} , /* initializers for row indexed by 0 */
{4, 5, 6, 7} , /* initializers for row indexed by 1 */
{8, 9, 10, 11} /* initializers for row indexed by 2 */
};
其中用来标志每一行的内嵌的花括号是可选的。下面的初始化尽管有点不清楚,但与前面的声明完全等价:
int ia[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
与一维数组一样,有些元素将不使用初始化列表提供的初始化式进行初始化。下面的声明只初始化了每行的第一个元素:
int ia[3][4] = {{ 0 } , { 4 } , { 8 } };
以下声明初始化了第一行的元素,其余元素都被初始化为 0。
int ia[3][4] = {0, 3, 6, 9};
如果表达式只提供了一个下标,则结果获取的元素是该行下标索引的内层数组。如 ia[2] 将获得ia 数组的最后一行,即这一行的内层数组本身,而并非该数组中的任何元素。