转载请注明原文出处:http://blog.youkuaiyun.com/roddick621
以下的知识点全部都是来自于C++ Primer 第四版,如果需要详细了解的话,可以查看原版的书籍。
C++提供了两种类似于vector和迭代器类型的低级符合类型——数组和指针。与vector类型相似,数组也可以保存某种类型的一组对象;而他们的区别在于,数组的长度是固定的。数组已经创建,就不允许添加新的元素。指针则可以想迭代器一样用于遍历和检查数据中的元素。
1.数组
数组是由类名,表舒服和维数组成的符合数据类型,类型名规定了存放在数组中的元素类型,而维数则指定了数组中包含的元素个数。
1.1 数组的定义和初始化
数组的维数必须用值大于等于1的常量表达式定义。此常量表达式只能包含整形字面值常量、枚举常量或者用常量表达式初始化的整型const对象。非const变量以及要到运行阶段才知道其值的const变量都不能用于定义数组的维数。数组的维数必须在[]内指定,eg: int buffer[1024];
1.显示初始化数组元素
在定义数组时,可为其元素提供一组用逗号分隔的初值,这些初值用花括号{}括起来,称为初始化列表:
如果没有显式提供元素初值,则数据元素会像普通变量一样初始化:int ia[3] = {0,1,2};
- 在函数体外定义的内置数组,起元素均初始化问为0;
- 在函数体内定义的内置数组,起元素无初始化;
- 不管数组在哪里定义,如果其元素为类类型,则自动调用该类的默认构造函数进行初始化
显示初始化数据不需要指定数组的维数值:eg int ia[] = { 0, 1, 2} ;
如果指定了数组维数,那么初始化列表提供的元素个数不能超过维数值。
2.当用一个字符串字面值进行初始化的时候,会在字符串字面值的尾端包含一个空字符(null)用于结束字符串。
char cal1[] = {'c','+', '+' }; //尾端没有空字符 char cal2[] = {'c','+', '+', '\0' }; //尾端有空字符 char cal3 = "C++"; //与cal2相同,尾端默认添加空字符
3.与vector不同,一个数组不能用另外一个数组来初始化,也不能讲一个数组复制给另一个数组。
1.2 数组操作
与vector元素一样,数组元素可以用下标操作符来访问,数组元素也是从0开始计数。
在用下标访问元素时,vector使用vector::size_type作为下标的类型,而数组下标的正确类型是size_t。在使用数组时,必须保证下标值在正确范围之内。
2.指针的引入
指针是指向某种类型对象的复合数据类型,是用于数组的迭代器:指向数组中的一个元素。在指向数组元素的指针上使用解引用操作符和自增操作符,与在迭代器上的用法类似。
2.1 什么是指针
指针的概念很简单:指针用于指向对象。具体来说,指针保存的是另一个对象的地址。
2.2 指针的定义和初始化
每个指针都有一个与之关联的数据类型,该数据类型决定了指针所指向的对象的类型。
1.指针变量的定义:C++语言使用*符号把一个标识符声明为指针:
2.3 指针操作
指针提供简介操作其所指对象的功能。与迭代器一样,对指针进行解引用可访问它所指的对象,*操作符(解引用操作符)讲获取指针所指的对象。
1.生成左值的解引用操作。解引用操作符返回指向对象的左值,利用这个功能可以修改指针所指对象的值。
2.指针和引用的比较。虽然使用引用和指针都可以简介访问另一个值,但他们之间有两个重要的区别。第一个区别在于引用永远指向某个对象,定义引用时没有初始化是错误的。第二个重要区别择食赋值行为的差异:给引用赋值修改的是该引用所关联的对象的值,而不是使引用与另外一个对象关联。引用一经初始化,就始终指向同一个特定对象(这就是为什么引用必须在定义时初始化的原因)。
3.指向指针的指针。指针本身也是可用指针指向的内存对象。这真占用内存空间存放其值,因此指针的存储地址可存放在指针中。eg:
int ival = 1024; int *pi = &ival; // pi points to an int int **ppi = &pi // ppi point to a pointer to int
2.4 使用指针访问数组元素。
C++语言中,指针和数组密切相关。特别是在表达式中使用数组名时,该名字会自动转换问指向数组第一个元素的指针,eg: int ia[] = {0,2}; int *ip=ia;
1.指针的算术操作。 使用指针的算术操作在指向某个元素的指针上加上(或减去)一个整型数值,就可以计算出指向数组另一个元素的指针值。指针相减操作结果是标准库类型ptrdiff_t数据类型。ptrdiff是signed整型。
2.解引用和指针算术操作之间的相互作用。在指针上加上一个整型数值,其结果仍然是指针。允许在这个结果上直接引用操作,而不必先把它赋给一个新指针。
3.下标和指针。使用下标访问数组时,实际上是使用下标访问指针。只要指针指向数组元素,就可以对它进行下标操作:
4.计算数组的超出末端指针。与vector提供的end操作类似,可以计算出数组的超出末端指针的值。int *p = &ia[2]; //p指向ia[2] int j = p[1] ; //j 的值等于ia[3] int k = p[-2] ; // k的指为ia[0]
这个p2值能够用来与其他指针比较,或者用来做指针算术操作表达式的操作数。对p2进行解引用讲得到无效值。int arr [arr_size] = {1,2,…}; int *p = arr ; int *p2 = p +arr_size;
5.输出数组元素。C++允许使用指针遍历数组。与其他内置类型一样,数组也没有成员函数。因此,数组不提供begin和end操作,所以只能自己给指针定位,使之分别标志数组的其实位置和超出末端位置。
6.指针是数组的迭代器。内置数组类型具有标准库容器的许多性质,与数组联合使用的指针本身就是迭代器。
2.5 指针和const限定符
1.指向const对象的指针。 如果指针指向const对象,则不允许用指针来该表其所指的const值。为了保持这个特性,C++语言强制要求指向const对象指针也必须具有const特性。
const double *cptr; // cptr是指向const double类型的指针 const double pr = 3.14 double *ptr = &pr ; //错误,ptr没有const特性 const double *cptr = ≺ //正确。 const void *cpr = &pr ; //必须用const void* 类型的指针来保存const对象的指针,不能用void*指针。 //允许把const对象的地址赋给指向const对象的指针。 double dval=3.14; cptr = &dval; //但是任何通过指针cptr修改其值的行为都会导致编译时的错误。
2.const指针。const指针本身的值不能修改。
与其他const变量一样,const指针的值不能修改,const指针与任何const变量一样,也必须在定义时初始化。指针本身是const的事实并没有说明是否能使用该指针去修改它所指向对象的值,指针所指对象的值能够修改完全取决于该对象的类型。int errNumb = 0; int *const curErr = &errNumb; //curErr是指向int类型对象的const指针。
3.指向const对象的const指针。它既不能修改pi_ptr所指向对象的值,也不能修改该指针的指向。
const double pr = 3.141519 const double *const pr_ptr = &pr ; //pi_ptr首先是一个const指针,指向double类型的const对象。
4.指针和typedef
声明const pstring时,const修饰的是pstring的类型,这是一个指针。因此,该声明语言应该是把cstr定义为指向string类型对象的const指针,等价于:string *const cstr;typedef string *pstring; const pstring cstr;
3.C风格字符串
C风格字符串就是以空字符null结束的字符数组。
1.C风格字符串的使用。C++语言通过(const) char *类型的指针来操纵C风格字符串。一般来说,可以使用指针的算术操作来遍历C风格字符串,每次都对指针进行测试并递增1,知道遇到结束符null为止。
2.C风格字符串的标准库函数。要使用这些标准库,必须包含对应的头文件 : #include <cstring> cstring是string.h头文件的C++版本,而string.h则是C语言提供的标准库。
传递给这些标准库例程的指针必须具有非零值,并且指向以null结束的字符数组中的第一个元素。其中一些标准库函数会修改传递给它的字符串,这些函数讲假定他们所修改的字符串具有足够大的空间接受本函数新生成的字符,程序员必须确保目标字符足够大。
操纵C风格字符串的标准库函数 strlen(s) 返回s的长度,不包括字符串结束符null strcmp(s1,s2) 比较两个字符串s1和算s2是否相同。若s1与s2相等,返回0;若s1大于s2,返回正数,若s1小于s2,则返回负数。 strcat(s1,s2) 将字符串s2连接到s1后,并返回s1。 strcpy(s1,s2) 讲s2复制给s1,并返回s1。 strncat(s1, s2, n0) 讲s2的前n个字符连接到s1后面,并返回s1。 strncpy(s1, s2, n) 将s2的前n个字符复制给s1,并返回s1。 3.永远不要忘记字符串结束符null。标准库函数总是需要字符串以null为结束符的,否则计算的结果不可预料。
4.调用者必须确保目标字符串具有足够的大小。
5.使用strn函数处理C风格字符串。进行操作的时候需要正确计算出size实参的值,这样strn版本要比没有size参数的版本更加安全,但是要想目标数组复制货串接比其size更多的字符,数组溢出的现象仍然会发生。
6.尽可能使用标准库类型string。
3.1 创建动态数组
动态数组不必在编译的时候知道其长度,可以在运行的时候才确定数组长度。与数组变量不同,动态分配的数组将一直存在,知道程序显式释放它为止。每个程序在执行时都占用一块可用的内存,用于存放动态分配的对象,次内存空间称为程序的自由存储区(free store)或堆(heap)。C语言使用malloc和free来分配,C++则使用new和delete来实现相同的功能。
1.动态数组的定义。数组变量通过指定类型,数组名和维数来定义。而动态分配数组时,只需要指定类型和数组长度,不必为数组对象命名,new表达式返回指向新分配数组的第一个元素的指针:int *pia = new int[10];
2.初始化动态分配的数组。动态分配数组时,如果数组元素具有类类型,则使用默认构造函数来初始化;如果数组元素是内置类型,则无初始化。也可以使用跟在数组藏毒后面的一堆空圆括号,对数组元素做初始化:
对于动态分配的数组,其元素值可以初始化为该类型的默认值,而不能想数组变量一样,用初始化列表进行初始化。string *psa = new string[10] //初始化为空 int *pia = new int[10]; //没有初始化 int *pia2 = new int[10](); //初始化为默认值
3.const对象的动态数组。
如果在自由存储区中创建的数组存储了内置类型的const对象,则必须为这个数组提供初始化,因为数组元素都是const对象,无法赋值。
4.允许动态分配空数组。用new穿件长度为0的数组时,new返回有效的非零指针。该指针与new返回的其他指针不同,不能进行解引用操作,因为它毕竟没有指向任何元素。而允许比较运算,加(减)0运算。
5.动态空间的释放。动态分配的内存最后必须进行释放,否则内存将会被耗尽。程序员必须显式地用delete []来释放不在需要使用到的动态数组的内存空间。eg:delete [] pid; 这里的空方括号是必不可少的,它告诉编译器该指针指向的是自由存储区中的数组,而非单个对象。
3.2 新旧代码的兼容
1.混合使用标准库string和C风格字符串。
- 可以使用C风格字符串对string对象进行初始化或赋值。
- string类型的加法操作需要两个操作数,可以使用C风格字符串作为其中的一个操作数,也允许将C风格字符串作符合赋值操作的右操作数。
反之则不成立:在要求C风格字符串的地方不可以直接使用标准库string类型对象。但是string类提供了一个名为c_string的成员函数实现我们的要求:
string st3("Helloworld"); const char *str = st3.c_str() ; //因为c_str返回的指针指向const char类型的数组。
2.使用数组初始化vector对象。使用数组初始化vector对象的时候,必须支出用于初始化的第一个元素已经数组最后一个元素的下一位置的地址:
const size_t arr_size = 6;
int int_arr[arrsize] = {0,1,2,3,4,5}
vector<int> ivec(int_arr,int_arr+arr_size);
4.多维数组
严格地说,C++中并没有多维数组,通常所指的多为数组就是数组的数组。
1.多维数组的初始化。与普通数组一样,但是对多维数组的每一行,可以再用花括号指定其元素的初始化式。
2.多为数组的下标引用。为了多多维数组进行索引,每一维都西药一个下标。
3.指针和多为数组。与普通数组一样,使用多维数组名时,实际上将其自动转换为指向该数组第一个元素的指针。定义指向数组的指针与如何定义数组本身类似:首先声明元素类型,后接(数组)变量名字和维数。
int ia[3][4]; int (*ip)[4]; //指向大小为4的int型数组 int *ip1[4]; //Ip1为一个数组,保存int型指针,所以这里的()是必须加的。