四.数组和指针.
001 数组和vector的区别在于,数组的长度是固定的.数组一经创建,就不允许动态的修改长度.指针可以像迭代器一样用于遍历和检索数组中的元素.
现代C++程序应该尽量使用vector和迭代器类型,而避免使用低级的数组和指针.只有在强调速度时,才在类实现的内部使用数组和指针.
002 数组的缺陷在于没有size()操作,也就是程序员无法知道一个给定数组的长度.
如果需要更改数组的长度,就必须再声明一个更大的数组,把原来数组的内容copy到新数组中去.
003 引用不能定义数组.没有所有元素都是引用的数组.
004 非const变量和要到运行阶段才知道值的const变量都不能用作数组定义的维数.(这两点和C99标准不一样,动态创建参见第021项)
const unsigned buf_size = 512; // const 变量.
int staff_size = 217; // 非const变量.
const unsigned sz = get_size(); // 运行时才确定的const变量.
char buf1[buff_size]; // ok.
char buf2[buff_size + 1]; // ok, 常量计算表达式.
double salaries[staff_size]; // error, 非const变量做维数.
int test_rest[sz]; // error, 运行时才确定的const变量.
005 默认初始化.
在函数外定义的内置类型数组,元素都初始化0.
函数内定义的内置类型数组,元素都无初始化过程.
如果是类类型的数组,无论在哪里定义,都调用默认构造函数初始化.
006 显示初始化.
int ia[3] = {0, 1, 3};
char ca1[] = {'c', '+', '+'};
char ca2[] = {'c', '+', '+', '/0'};
char ca3[] = "c++"; // ca3和ca2有相同的维数和初始化值,含有四个元素.
char ca4[3] = "c++" // 这会产生一个编译错误,因为"c++"是一个长度为4的字符串.
string str_arr[3] = {"hi", "heipi"}; // str_arr[2]是一个值为空的string类型元素.
007 与vector不同,一个数组不能用另外一个数组初始化,也不能将一个数组赋值给另一个数组.
个别编译器扩展功能,支持数组复制,但是如果希望编写的程序能在不同的编译器上运行,应该避免这类应用.
008 数组下标的类型是size_t.
// 编译及运行结果
Nan@linux-g4n6:~/Documents> g++ -o o 04008.cpp
Nan@linux-g4n6:~/Documents> ./o
0 1 2 3 4 5 6 7 8 9
Nan@linux-g4n6:~/Documents>
程序员必须自己注意,防止数组越界,因为数组没有类似vector中size()的函数支持查询数组的长度.
其实可以考虑这个办法计算数组长度: size_t ia_size = sizeof(ia)/sizeof(*ia);
009 指针用于指向对象,具体的说,指针保存的是另一个对象的地址.
与迭代器不同:指针用于指向单个对象,通过解引用(*)访问元素,而迭代器只能用于访问容器内的元素.
string s("Hello heipi");
string *sp = &s; // sp指向了string s.
string s_arr[2] = {"Hello", "heipi"};
sp = s_arr; // sp指向了string数组的第一个元素"Hello"
要理解指针声明语句,请从右向左阅读.
010 指针定义时,必须初始化(指向具体的对象,或者是初始化为0, 或NULL,NULL定义在cstdlib头文件中).
对指针进行初始化或者赋值的的四种类型:
1.0值"常量"表达式,注意,用值为0的int变量来初始化一个指针是错误用法.
2.类型匹配对象的地址.
3.匹配对象的下一个地址.
4.同类型的另一个有效的指针.
011 void*指针,可以保存任何类型对象的地址.他表明该指针与一个地址相关,但不清楚存储在此地址上的对象的类型.
const void* 指针,可以用于保存const类型对象的地址.
double pi = 3.14;
double *dp = π
void *vp = π
vp = dp;
012 指针通过*操作获取对象,可以访问,也可以修改.
cout << *dp;
*dp = 9999;
cout << *dp;
013 指针和引用:引用总是指向某个固定对象,定义时必须初始化,不可将引用修改为指向别的对象.
对引用的修改,是修改其指向对象的值.对指针的修改,使指针指向新的对象.
double &dp1 = pi;
double obj1 = 2.34;
dp1 = obj1; // 此时pi变为了2.34,和obj1值相同.
dp = obj1; // 此时dp不再指向pi, pd指向了obj1.
014 指向指针的指针.
double **dpp = &dp;
cout << **dpp;
015 指向数组的指针.
const int MAXSIZE = 5;
int ia[MAXSIZE] = { 0, 2, 4, 6, 8};
int *ip = ia; // 在表达式中使用数组名时,该名字会自动转换为指向数组的第一个元素的指针.
ip = &ia[3] // ip指向元素6.
016 指针的算术运算.
int *ip2 = ip + 1; // ip2 指向元素8.
ptrdiff_t dist = ip2 - ip; // ptrdiff定义在cstddef头文件中.
cout << *(ip + 1); // 等同于cout << *ip2, 需要注意括号的应用.
017 C++允许指针指向数组或对象的超出末端的地址,但不允许对此地址进行解引用操作.
int *ip_head = ia;
int *ip_end = ip_head + MAXSIZE; // 可以引用此地址,但不能解引用其中的值(无意义的值).
for (int *tmp = ip_head; tmp != ip_end; ++tmp){ // 这里,指针就是数组的迭代器.
cout << *tmp << std::endl;
}
018 指向const对象的指针也必须是const类型的,不能通过解引用修改const指针指向的对象的值.
const int *ip3 = &MAXSIZE; // ip3指向了MAXSIZE,但是不能通过*ip3修改MAXSIZE的值.
int iTmp = 9;
ip3 = &iTmp; // iTmp虽然是非const类型,ip3可以指向iTmp,但仍然不能通过*ip3来修改.
把一个const对象的地址赋给一个普通的,非const对象的指针也是错误的.
int *ip4 = &MAXSIZE; // error, ip4不是const对象的指针.
指向const对象的指针常用于形式参数,用来防止意外的修改实际变量.
019 const指针,意味着指针本身不能修改,const在*后边.
int counter = 9;
int *const ip5 = &counter; // ip5是指向counter的指针.
*ip5 = 999; // ok,可以通过*ip5修改其值.
ip5 = ia; // error,就是不能将ip5指向其他类型.
ip5 = ip5; // error,嘿嘿,ip5赋给自己也不行,别费心思了.
指向const对象的const指针.
const int *const ip6 = &MAXSIZE; // ip6指向MAXSIZE,可以通过*ip6引用MAXSIZE的值,别无他用.
特别注意const与typedef结合使用容易出错的地方.
typedef string *pstring; // pstring是一个指向string类型的指针.
const pstring cstr; // cstr是一个string *const的变量,也就是指向string类型的const指针.
// 有人误解这是个const string *的类型,还记得么?const是说明cstr的!
pstring const cstr1; // ok,这个声明和上边cstr的声明是相同的含义.
020 字符串字面值的类型,就是const char类型的数组.C++中,不应该继续使用这种类型,因为它是大量安全问题的根源.
const char *cp = "some value";
while(*cp){
cout << *cp;
++cp;
}
C风格字符串的标准函数库,连接和复制的操作都必须保证s1有足够大的空间,否则后果将极度危险.
#include<cstring>;
strlen(s); // 返回s的长度,不包括字符串结束符.
strcmp(s1,s2); // 字典顺序比较s1 s2两个字符串的大小,返回0表示相同,正数表示s1大于s2.
strcat(s1,s2); // 将s2连接到s1后,并返回s1.
strcpy(s1,s2); // 将s2复制到s1中.
strncat(s1,s2,n); // 将s2的前n个字符,连接到s1后边,并返回s1.
strncpy(s1,s2,n); // 将s2的前n个字符,复制到s1中,并返回s1.
永远也不要忘记字符串结束符NULL.
021 用于存放动态分配对象的内存空间称为自由存储区或"堆".
c使用malloc和free分配,而c++用new和delete.
定义动态数组.
int *ipa1 = new int[10]; // 由于int是内置类型,所以ipa是没有初始化过的.
int *ipa2 = new int[10](); // ipa2都初始化为0.
string *spa = new string[5]; // spa分配5个string空间,每个string对象都初始化为empty.
new可以创建长度为0的指针,此时返回的地址是有效的,可以比较,但是不能解引用.
size_t num = get_size(); // 通get_size()函数返回数组的长度.
int *ipa3 = new int[num](); // ok .
const对象的动态数组.
const int *pi1 = new const int[10]; // error,这样是不行的,必须有初始化.
const int *pi2 = new const int[10](); // ok, 都初始化为0.
用delete [] 释放数组空间,如果遗漏了空方括号,将导致程序运行时的错误.这样会导致少释放了内存空间,产生内存泄漏.
delete [] ipa1;
022 对string类型,可以使用c_str函数返回const char类型的数组.
const char *str = s.c_str();
023 使用数组初始化vector对象.
const size_t arr_size = 6;
int int_arr[arr_size] = {0, 1, 2, 3, 4, 5};
vector<int> ivec1(int_arr, int_arr + arr_size); // ivec1包含全部int_arr的元素.
vector<int> ivec2(int_arr + 1,int_ary + 3); // ivec2包含1,2,3三个元素.
024 多维数组.在C++中,也就是数组的数组.
下边两个初始化的结果是相同的.
int ia1[3][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11}};
int ia2[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
指向多维数组的指针.
int *ip[4]; // 指向整数的指针数组, ip[0],ip[1],ip[2],ip[3]都是指向整数的指针.
int (*ip)[4] = ia; // ip是一个指针,指向含有四个整数类型元素的数组指针.
// 下边两个定义合起来等同于上边的一条定义.
typedef int int_array[4];
int_array *ip = ia;
for(int_array *p = ia; p!= ia + 3; ++p)
for(int *q = *p; q != *p + 4; ++q)
cout << *q << endl;
001 数组和vector的区别在于,数组的长度是固定的.数组一经创建,就不允许动态的修改长度.指针可以像迭代器一样用于遍历和检索数组中的元素.
现代C++程序应该尽量使用vector和迭代器类型,而避免使用低级的数组和指针.只有在强调速度时,才在类实现的内部使用数组和指针.
002 数组的缺陷在于没有size()操作,也就是程序员无法知道一个给定数组的长度.
如果需要更改数组的长度,就必须再声明一个更大的数组,把原来数组的内容copy到新数组中去.
003 引用不能定义数组.没有所有元素都是引用的数组.
004 非const变量和要到运行阶段才知道值的const变量都不能用作数组定义的维数.(这两点和C99标准不一样,动态创建参见第021项)
const unsigned buf_size = 512; // const 变量.
int staff_size = 217; // 非const变量.
const unsigned sz = get_size(); // 运行时才确定的const变量.
char buf1[buff_size]; // ok.
char buf2[buff_size + 1]; // ok, 常量计算表达式.
double salaries[staff_size]; // error, 非const变量做维数.
int test_rest[sz]; // error, 运行时才确定的const变量.
005 默认初始化.
在函数外定义的内置类型数组,元素都初始化0.
函数内定义的内置类型数组,元素都无初始化过程.
如果是类类型的数组,无论在哪里定义,都调用默认构造函数初始化.
006 显示初始化.
int ia[3] = {0, 1, 3};
char ca1[] = {'c', '+', '+'};
char ca2[] = {'c', '+', '+', '/0'};
char ca3[] = "c++"; // ca3和ca2有相同的维数和初始化值,含有四个元素.
char ca4[3] = "c++" // 这会产生一个编译错误,因为"c++"是一个长度为4的字符串.
string str_arr[3] = {"hi", "heipi"}; // str_arr[2]是一个值为空的string类型元素.
007 与vector不同,一个数组不能用另外一个数组初始化,也不能将一个数组赋值给另一个数组.
个别编译器扩展功能,支持数组复制,但是如果希望编写的程序能在不同的编译器上运行,应该避免这类应用.
008 数组下标的类型是size_t.
//
04008.cpp
#include < iostream >
using std::cout;
using std::endl;
int main() ... {
const int MAX = 10;
int ia[MAX] = ...{0,1,2,3,4,5,6,7,8,9};
for(size_t ix = 0; ix < MAX; ++ix)
cout << ia[ix] << " ";
cout << endl;
return 0;
}
#include < iostream >
using std::cout;
using std::endl;
int main() ... {
const int MAX = 10;
int ia[MAX] = ...{0,1,2,3,4,5,6,7,8,9};
for(size_t ix = 0; ix < MAX; ++ix)
cout << ia[ix] << " ";
cout << endl;
return 0;
}
Nan@linux-g4n6:~/Documents> g++ -o o 04008.cpp
Nan@linux-g4n6:~/Documents> ./o
0 1 2 3 4 5 6 7 8 9
Nan@linux-g4n6:~/Documents>
程序员必须自己注意,防止数组越界,因为数组没有类似vector中size()的函数支持查询数组的长度.
其实可以考虑这个办法计算数组长度: size_t ia_size = sizeof(ia)/sizeof(*ia);
009 指针用于指向对象,具体的说,指针保存的是另一个对象的地址.
与迭代器不同:指针用于指向单个对象,通过解引用(*)访问元素,而迭代器只能用于访问容器内的元素.
string s("Hello heipi");
string *sp = &s; // sp指向了string s.
string s_arr[2] = {"Hello", "heipi"};
sp = s_arr; // sp指向了string数组的第一个元素"Hello"
要理解指针声明语句,请从右向左阅读.
010 指针定义时,必须初始化(指向具体的对象,或者是初始化为0, 或NULL,NULL定义在cstdlib头文件中).
对指针进行初始化或者赋值的的四种类型:
1.0值"常量"表达式,注意,用值为0的int变量来初始化一个指针是错误用法.
2.类型匹配对象的地址.
3.匹配对象的下一个地址.
4.同类型的另一个有效的指针.
011 void*指针,可以保存任何类型对象的地址.他表明该指针与一个地址相关,但不清楚存储在此地址上的对象的类型.
const void* 指针,可以用于保存const类型对象的地址.
double pi = 3.14;
double *dp = π
void *vp = π
vp = dp;
012 指针通过*操作获取对象,可以访问,也可以修改.
cout << *dp;
*dp = 9999;
cout << *dp;
013 指针和引用:引用总是指向某个固定对象,定义时必须初始化,不可将引用修改为指向别的对象.
对引用的修改,是修改其指向对象的值.对指针的修改,使指针指向新的对象.
double &dp1 = pi;
double obj1 = 2.34;
dp1 = obj1; // 此时pi变为了2.34,和obj1值相同.
dp = obj1; // 此时dp不再指向pi, pd指向了obj1.
014 指向指针的指针.
double **dpp = &dp;
cout << **dpp;
015 指向数组的指针.
const int MAXSIZE = 5;
int ia[MAXSIZE] = { 0, 2, 4, 6, 8};
int *ip = ia; // 在表达式中使用数组名时,该名字会自动转换为指向数组的第一个元素的指针.
ip = &ia[3] // ip指向元素6.
016 指针的算术运算.
int *ip2 = ip + 1; // ip2 指向元素8.
ptrdiff_t dist = ip2 - ip; // ptrdiff定义在cstddef头文件中.
cout << *(ip + 1); // 等同于cout << *ip2, 需要注意括号的应用.
017 C++允许指针指向数组或对象的超出末端的地址,但不允许对此地址进行解引用操作.
int *ip_head = ia;
int *ip_end = ip_head + MAXSIZE; // 可以引用此地址,但不能解引用其中的值(无意义的值).
for (int *tmp = ip_head; tmp != ip_end; ++tmp){ // 这里,指针就是数组的迭代器.
cout << *tmp << std::endl;
}
018 指向const对象的指针也必须是const类型的,不能通过解引用修改const指针指向的对象的值.
const int *ip3 = &MAXSIZE; // ip3指向了MAXSIZE,但是不能通过*ip3修改MAXSIZE的值.
int iTmp = 9;
ip3 = &iTmp; // iTmp虽然是非const类型,ip3可以指向iTmp,但仍然不能通过*ip3来修改.
把一个const对象的地址赋给一个普通的,非const对象的指针也是错误的.
int *ip4 = &MAXSIZE; // error, ip4不是const对象的指针.
指向const对象的指针常用于形式参数,用来防止意外的修改实际变量.
019 const指针,意味着指针本身不能修改,const在*后边.
int counter = 9;
int *const ip5 = &counter; // ip5是指向counter的指针.
*ip5 = 999; // ok,可以通过*ip5修改其值.
ip5 = ia; // error,就是不能将ip5指向其他类型.
ip5 = ip5; // error,嘿嘿,ip5赋给自己也不行,别费心思了.
指向const对象的const指针.
const int *const ip6 = &MAXSIZE; // ip6指向MAXSIZE,可以通过*ip6引用MAXSIZE的值,别无他用.
特别注意const与typedef结合使用容易出错的地方.
typedef string *pstring; // pstring是一个指向string类型的指针.
const pstring cstr; // cstr是一个string *const的变量,也就是指向string类型的const指针.
// 有人误解这是个const string *的类型,还记得么?const是说明cstr的!
pstring const cstr1; // ok,这个声明和上边cstr的声明是相同的含义.
020 字符串字面值的类型,就是const char类型的数组.C++中,不应该继续使用这种类型,因为它是大量安全问题的根源.
const char *cp = "some value";
while(*cp){
cout << *cp;
++cp;
}
C风格字符串的标准函数库,连接和复制的操作都必须保证s1有足够大的空间,否则后果将极度危险.
#include<cstring>;
strlen(s); // 返回s的长度,不包括字符串结束符.
strcmp(s1,s2); // 字典顺序比较s1 s2两个字符串的大小,返回0表示相同,正数表示s1大于s2.
strcat(s1,s2); // 将s2连接到s1后,并返回s1.
strcpy(s1,s2); // 将s2复制到s1中.
strncat(s1,s2,n); // 将s2的前n个字符,连接到s1后边,并返回s1.
strncpy(s1,s2,n); // 将s2的前n个字符,复制到s1中,并返回s1.
永远也不要忘记字符串结束符NULL.
021 用于存放动态分配对象的内存空间称为自由存储区或"堆".
c使用malloc和free分配,而c++用new和delete.
定义动态数组.
int *ipa1 = new int[10]; // 由于int是内置类型,所以ipa是没有初始化过的.
int *ipa2 = new int[10](); // ipa2都初始化为0.
string *spa = new string[5]; // spa分配5个string空间,每个string对象都初始化为empty.
new可以创建长度为0的指针,此时返回的地址是有效的,可以比较,但是不能解引用.
size_t num = get_size(); // 通get_size()函数返回数组的长度.
int *ipa3 = new int[num](); // ok .
const对象的动态数组.
const int *pi1 = new const int[10]; // error,这样是不行的,必须有初始化.
const int *pi2 = new const int[10](); // ok, 都初始化为0.
用delete [] 释放数组空间,如果遗漏了空方括号,将导致程序运行时的错误.这样会导致少释放了内存空间,产生内存泄漏.
delete [] ipa1;
022 对string类型,可以使用c_str函数返回const char类型的数组.
const char *str = s.c_str();
023 使用数组初始化vector对象.
const size_t arr_size = 6;
int int_arr[arr_size] = {0, 1, 2, 3, 4, 5};
vector<int> ivec1(int_arr, int_arr + arr_size); // ivec1包含全部int_arr的元素.
vector<int> ivec2(int_arr + 1,int_ary + 3); // ivec2包含1,2,3三个元素.
024 多维数组.在C++中,也就是数组的数组.
下边两个初始化的结果是相同的.
int ia1[3][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11}};
int ia2[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
指向多维数组的指针.
int *ip[4]; // 指向整数的指针数组, ip[0],ip[1],ip[2],ip[3]都是指向整数的指针.
int (*ip)[4] = ia; // ip是一个指针,指向含有四个整数类型元素的数组指针.
// 下边两个定义合起来等同于上边的一条定义.
typedef int int_array[4];
int_array *ip = ia;
for(int_array *p = ia; p!= ia + 3; ++p)
for(int *q = *p; q != *p + 4; ++q)
cout << *q << endl;