大二寒假读书笔记150130

C++提供了两种类似于vector和迭代器的低级符合类型———数组和指针。

数组的长度是固定的,一经创建就不允许添加新的元素。指针则可以像迭代器一样用于遍历和检查数组中的元素。

现代C++程序应尽量使用vector和迭代器类型,避免使用低级的数组和指针,设计良好的程序只有在强调速度时才在类的内部用到数组和指针。

1、数组没有获取容量大小的size操作,也不提供push_back操作。如果要更改数组的长度,程序猿只能创建一个更大的新数组,然后把原数组所有元素复制到新数组空间去。与使用标准vector类型的程序相比,依赖于内置数组的程序更容易出错并且难以调试。在出现标准库之后,数组被严格限制于程序内部使用,只有当性能测试表明使用vector未达到速度要求时才考虑使用数组。

2、数组的维数必须用值大于等于1的常量表达式定义。此常量表达式只能包含整型字面值常量、枚举常量或者用常量表达式初始化的整型const对象。非const变量以及要到运行阶段才知道其值的const变量都不能用于定义数组的维数。

//both buf_size and max_files are const
const unsigned buf_size = 512,max_files = 20;
int staff_size = 27;
const unsigned sz = get_size();//const value not known until run time
char input_buffer[buf_size];//ok
string fileTable[maxfiles+1];//ok
double salaries[staff_size];//error
int test_scores[get_sizes];//error
int vals[sz];//error
对于sz,尽管它是一个const对象,但它的值要到运行时调用get_size()函数才知道,因此不能用来初始化数组的位数。

3、

const unsigned array_size = 3;
int ia[array_size] = {0,1,2};//显示初始化数组元素,初始化列表
如果没有显示提供元素处置,则数组元素会像普通变量一样初始化:

函数体外的元素初始化为0

函数体内的元素无初始化。

如果元素为类类型,则不管在哪里初始化,自动调用该类的默认构造函数;如果没有,则必须对元素进行显示初始化。

显示初始化的数组不需要指定数组的维数,编译器会根据元素个数来确定数组的长度。

int ia[] = {0,1,2,3};//长度为4的数组

如果指定了数组位数,则初始化列表提供的元素个数不能超过元素值。如果维数大于列出的元素初值个数,则只初始化前面的数组元素:剩下的其他元素若是内置类型则初始化为0,若是类类型则调用该类的默认构造函数进行初始化。

4、特殊的字符数组(char类型)

char ca1[] = {'c','+','+'};//no null,维数3
char ca2[] = {'c','+','+','\0'};//ecplicit null,维数4
char ca3[] = "c++";//null terminator added automatically,维数4
字符数组可以用初始化列表初始化,也可以用一个字符串字面值初始化。用字符串字面值初始化时会自动加上一个额外的空字符(null)来结束字符串.所以在定义数组长度是不妨定义的大一些,以免出现下面错误:

const char ca4[5] = "hello";//error,hello is 6 elements 

另外,千万不允许数组直接复制和赋值(没有操作符重载的话)

int ia1[] = {0,1,2};
int ia2[](ia);//error
int main(){
    const unsigned array_size = 3;
    int ia3[array_size];//ok,but dangerous
    ia3 = ia;//error
}
一个数组不允许用另一个数组初始化,也不能将一个数组赋值给另一个数组。
警告:数组长度固定,但是如果必须在数组中添加新元素,程序猿就必须自己管理内存:要求系统重新分配一个新的内存空间来存放更大的数组,然后把原数组的所有元素复制到新分配的内存空间中。

5、数组操作大部分是通过下标来访问,在使用下标进行元素操作时必须注意下标不能越界。导致安全问题最常见的原因是所谓的“缓冲区溢出”错误。当我们编程时没有检查下标,并且引用了越出数组或其他类似数据结构边界的元素时就会导致这类错误。

6、指针是指向某种类型对象的复合数据类型。解引用操作符(*)和自增操作符与在迭代器上的用法类似。另外,和迭代器不同,指针用于指向单个对象,而迭代器只能用于访问容器内的元素(容器内的元素不也是单个对象吗?)。

具体的说,指针保存的是另一个对象的地址:

string s("hello world");
string *sp = &s;//sp holds the address of s
*sp前面的*表明变量sp是一个指针变量,&s中的&是取地址操作符。将s的地址赋给指针变量sp.取地址操作符只能用于左值,因为只有当变量用作左值时,才能取地址。同样的,由于用作vector类型、string类型或内置数组的下标操作和解引用操作生成左值,因此可对这两种操作的结果做取地址操作,这样即可获取某一特定对象的存储地址。

建议:指针和数组容易产生不可预料的错误。一部跟是概念上的问题:指针用于低级操作(机器操作?),容易产生与繁琐细节相关的错误,现代C++程序采用vector和迭代器来取代一般的数组、采用string来取代C风格字符串。

7、指针的声明不说多了,就在变量名称前面加*,在理解声明语句时,请从右向左阅读。另外注意区分:

string* ps1,ps2;//ps1 is a pointer ,ps2 is a string
string* ps3,*ps4;//ps3 and ps4 are pointer to string
再来看下指针的取值,一个有效的指针必然是以下三种状态之一:保存一个特定对象的地址;指向某个对象后面的另一元素;或者是0值。若指针保存0值,表明它不指向任何对象。未初始化的指针是无效的,只有赋值后才可以使用。

int ival = 1024;
int *pi = 0;
int *pi2 = &ival;
int *pi3;//ok,but dangerous,uninitalized
pi = pi2;
pi2 = 0;

上面这些都是正确的,不过特别注意不要只用未定义的指针。对于大多数编译器来说,如果使用未初始化的指针,会将指针中存放的不确定值视为地址,然后操纵该内存中存放的位内容。C++语言无法检测指针是否未被初始化,也无法区分有效地址和由指针分配到的存储空间存放的二进制位形成的地址。建议程序猿在使用之前初始化所有的变量,尤其是指针。有可能的话,除非所指向的对象已经存在,否则不要定义指针。如果必须分开定义指针和其所指向的对象,则将指针初始化为0.因为编译器可检测出0值的指针,程序可判断该指针并未指向一个元素。

8、指针初始化和赋值操作的约束

对指针进行初始化或赋值只能使用以下4种类型的值:

0值常量表达式,编译时可活的0值的整型const对象或字面值常量0

类型匹配的对象的地址

另一对象末的下一地址

同类型的另一个有效指针

把int型变量赋给指针是非法的,尽管此int变量的值可能为0.但允许把数值0或在编译时获得0值的const量赋给指针

int ival;
int zero = 0;
 const int c_ival = 0;
int *pi = ival;//error
pi = zero;//error
pi = c_ival;//ok
pi = 0;//ok
此外,还可以用从C语言继承下来的预处理器变量NULL,该变量在cstdlib头文件中定义,其值为0,等效于初始化为0值

预处理器变量不是在std命名空间中定义的,因此其名字应为NULL,而非std::NULL

9、指针类型匹配

除了在4.2.5和15.3介绍的两种例外情况外,指针只能初始化或者赋值为同类型的变量地址或另一指针:

double dval;
double *pd = &dval;//ok
double *pd2 = pd;//ok
int *pi = pd;//error
pi = &dval;//error

此外,C++提供了一种特殊的指针类型void*,它可以保存任何类型对象的地址,但不清楚存储在此地址上的对象的类型。

它只支持几种有限的操作:与另一个指针进行比较;向函数传递void*指针或从函数返回void*指针;给另一个void*指针赋值。不允许用void*指针操纵它所指向的对象!
















你可能感兴趣的:(C++)