c++ primer 第四章 数组和矩阵

来源:互联网 发布:微信互粉源码 编辑:程序博客网 时间:2024/06/12 19:10
第一、数组:定义和初始化、数组操作。
第二:指针的引入:定义、初始化指针操作、使用指针访问数组元素、指针和const限定符
第三:C风格字符串:标准库函数、使用stern函数处理C风格字符串P116、尽可能使用string
第四:创建动态数组(不懂多维数组
小结:
①数组显著缺陷:长度固定,程序员无法知道一个给定数组的长度。
②数组定义中的类型名可以使内置数据类型或类类型;除引用之外,数组元素的类型可以是任意的复合类型。没有所有元素都是引用的数组。
③字符型数组注意null。
④如果没有显式的提供元素初值,数组会像普通变量一样初始化:
  a)在函数体定义的内置数组,其元素均初始化为0;b)在函数体定义的内置数组,其元素无初始化;
  c)不管数组在哪里定义,如果元素为类类型,则自动调用该类的默认构造函数进行初始化。此时,出来给元素赋值以外,
    其它使用这些元素的操作没有定义。
⑤不允许数组元素直接复制和赋值。
⑥指针用于指向对象,具体来说,指针保存另一个对象的地址。
⑦避免使用未初始化的指针。使用未初始化的指针,在运行时很可能引起崩溃。
⑧指针初始化和赋值操作的约束:
    a)0常量表达式,例如在编译时可获得0值的整型const对象或字面值常量0。
    b)类型匹配的对象的地址。
    c)另一对象之后的下一地址。
    d)同类型的另一个有效指针。
⑨指针和引用:
      a)引用总是指向某个对象:定义引用时没有初始化是错误的;
      b)赋值行为的差异:给引用赋值修改的是该引用相关的对象的值,而并不引用与另一个对象关联。
        引用一经初始化就始终指向同一个特定的对象,所以引用必须在定义是初始化。
⑩不要忘记字符串结束符null、调用者必须保证目标字符串具有足够的大小。
对于多为数组就是数组的数组

 

 

与vector 类似,数组也是容器

数组的缺陷:

1、长度固定

2、没有size 操作,故,无法获取容器大小

3、没有push_back 操作,无法向其添加元素

4、无法更改数组长度,只能创建新的更大的数组,然后,将原数组复制到新数组。


数组维数,必须是大于1 的常量表达式。

也就是说,必须是在编译时,就知道的值。如:const unsigned buf_size = 512;

凡是在运行时才知道的值,都不能定义数组的维数。

[cpp] view plaincopyprint?
  1. const unsigned buf_size = 512, max_files = 20;
  2. int staff_size = 27;
  3. char input_buffer[buf_size]; // 正确, buf_size 是 const
  4. string fileTable[max_files + 1]; // 正确, max_files+1 是常量表达式
  5. const unsigned sz = get_size();// 错误,get_size() 在运行时才知道,结果值
  6. double salaries[staff_size]; // 错误,staff_size 非const
  7. int test_scores[get_size()];// 错误,同 get_size()
  8. int vals[sz]; // 错误,同 get_size()


显示初始化数组:

[cpp] view plaincopyprint?
  1. // 显示初始化
  2. const unsigned array_size = 5;
  3. int iarr[array_size] = {0, 1, 2, 3, 4};
  4. // 省略维数
  5. int iarr2[] = {0, 1, 2, 3};
  6. // 相当于:{1, 2, 3, 0, 0}
  7. int iarr3[array_size] = {1, 2, 3};
  8. // 相当于:{"aa", "bb", "cc", "", ""}
  9. string sarr[array_size] = {"aa", "bb", "cc"};


特殊字符:

[cpp] view plaincopyprint?
  1. char ca1[] = {'C','+', '+'};// size: 3
  2. char ca2[] = {'C','+', '+','\0'}; // size:4
  3. char ca3[] = "C++";// size:4,末尾自动添加 null


数组不可直接复制或者赋值

[cpp] view plaincopyprint?
  1. char ca1[] = {'C','+', '+'};
  2. char ca2[] = ca1;// 错误

vector 的下标类型: vector::size_type
数组
的下标类型 : size_t
数组下标超出正确范围,即,发生数组越界。将会产生严重错误。类似的安全问题,称为:缓冲区溢出

[cpp] view plaincopyprint?
  1. const unsigned array_size = 5;
  2. int iarr1[] = {1, 2, 3, 4, 5};
  3. int iarr2[array_size];
  4. for(size_t i=0; i!=array_size; ++i)
  5. iarr2[i] = iarr1[i];

指针,&符号是:取地址操作符(address-of)

[cpp] view plaincopyprint?
  1. string s("This is a string");
  2. string *sp = &s;
注:指针多数用于低级操作,容易产生错误,尽量少用。
而通过 vector 、迭代器等方式取代一般的数组、指针的使用。 使用 string 类型,取代 C 风格字符串。


指针定义、初始化

[cpp] view plaincopyprint?
  1. vector<int> *pvec;
  2. int *ip1, *ip2;
  3. string* sp1, sp2; // sp1 是指针,而 sp2 只是 string

指针保存 0 值 或者 NULL ,表明不指向任何对象。(NULL 实际上等价于 0)
特殊的指针:void * ,可以保存任何类型对象的地址

[cpp] view plaincopyprint?
  1. int ival = 1024;
  2. int *pi = 0;
  3. int *pi2 = &ival;
  4. void *pi3 = pi2;
  5. pi = pi2;


数组的算术操作。 *pi2、*pi3 结果相同,但, int *pi3 = iarr + 4 表述更直观。
指针作减法操作,结果是 ptrdiff_t 类型的数据。
使用下标访问数组时,实际上,是使用下标访问了指针

[cpp] view plaincopyprint?
  1. int iarr[] = {1, 2, 3, 4, 5};
  2. int *pi = iarr;
  3. int *pi2 = &iarr[4];
  4. int *pi3 = iarr + 4;
  5. ptrdiff_t n = pi3 - pi;
  6. int i = *(iarr + 3); // iarr[3], 4
  7. int *pi4 = pi3 - 4; // &iarr[0]
  8. int x = pi3[-4]; // iarr[0] equals to pi3[-4]
  9. // 指向数组的超出末端的地址,可以计算,不能解引用
  10. int *ipend = iarr + 5;


指针是数组的迭代器

[cpp] view plaincopyprint?
  1. const size_t array_size = 3;
  2. int iarr[array_size] = {1, 2, 3};
  3. for (int *pbegin = iarr, *pend = iarr + array_size; pbegin != pend; ++pbegin)
  4. *pbegin = 100;
  5. vector<int> ivec(10, 0);
  6. for (vector<int>::iterator iter = ivec.begin(); iter!=ivec.end(); ++iter)
  7. *iter = 1;





指针和引用的区别

[cpp] view plaincopyprint?
  1. #include <iostream>
  2. #include <string>
  3. #include <bitset>
  4. using std::cin;
  5. using std::cout;
  6. using std::endl;
  7. using std::string;
  8. using std::bitset;
  9. int main()
  10. {
  11. int ival = 1024, ival2 = 2048;
  12. int *pi = &ival, *pi2 = &ival2;
  13. pi = pi2;
  14. *pi = 5000;
  15. cout << "pi:" << pi << ", pi2:" << pi2 << endl;
  16. cout << "*pi :" << *pi << ", *pi2 :" << *pi2 << endl;
  17. int &ri = ival, &ri2 = ival2;
  18. cout << *(&ri) <<endl;
  19. ri = ri2;
  20. cout << "ri:" << ri << ", ri2:" << ri2 << endl;
  21. cout << "&ri:" << &ri << ", &ri2:" << &ri2 << endl;
  22. system("pause");
  23. return 0;
  24. }


结果如下:




指向指针的指针

[cpp] view plaincopyprint?
  1. #include <iostream>
  2. #include <string>
  3. #include <bitset>
  4. using std::cin;
  5. using std::cout;
  6. using std::endl;
  7. using std::string;
  8. using std::bitset;
  9. int main()
  10. {
  11. int x = 10;
  12. int *pi = &x;
  13. int **ppi = &pi;
  14. cout << "x:" << x << endl;
  15. cout << "*pi:" << *pi << endl;
  16. cout << "**ppi:" << **ppi << endl;
  17. system("pause");
  18. return 0;
  19. }

结果如下

指针和 const 限定符

[cpp] view plaincopyprint?
  1. double dheight = 100.5; 
  2. const double dprice = 10.5; 
  3. const double *pd = &dprice;// pd 指向的 dprice 是const 的 
  4. pd = &dheight;  // pd 可以指向非const 的对象 
  5.  
  6. int ierr = 0; 
  7. int *const curErr = &ierr; // 指针 curErr, 是const的 
  8.  
  9. const int iMsg = 10; 
  10. const int *const pmsg = &iMsg; // 指针 和 指向的对象,都是const的 



 



指针和 typedef。以下:ps,ps2,ps3 都是等价的。
原因是:const 限定符可以放在类型前,也可以放在类型后
就是说,         const string a; 
也可以写成: string const a;

[cpp] view plaincopyprint?
  1. typedef string * pstring; 
  2. string s("Hello"); 
  3. const pstring ps = &s; 
  4. string *const ps2 = &s;  // 与上式,等价 
  5. pstring const ps3 = &s; // 与上式,等价 

此处,const 修饰的是 pstring 类型,也就是说,const 修饰的是指针。typedef,不是简单的文本扩展!

因此,const pstring ps = &s;   其实等价于:   string *const ps = &s;



C风格字符串

字符串字面值 的类型,是字符常量的数组。
也就是,const char 类型的数组

C++ 从C语言 继承下来的一种通用结构就是 C风格字符串(C-style character string)
而,字符串面值,就是该类型的实例。

实际上,C风格字符串既不能归结为C语言的类型,也不能归结为C++语言的类型,
而是以空字符 null 结束的字符数组。


[cpp] view plaincopyprint?
  1. char ca1[] = {'C','+', '+'}; 
  2. char ca2[] = {'C','+', '+','\0'}; // C风格 
  3. char ca3[] = "C++";    // C风格,末尾自动加 null 
  4.  
  5. const char *cp ="C++"; // C风格 
  6. char *cp1 = ca1;  // 
  7. char *cp2 = ca2;  // C风格 



 

C++ 通过 (const) char * 类型的指针,来操作C风格字符串。

[cpp] view plaincopyprint?
  1. const char *cp ="some value"
  2. while(*cp) 
  3.     ++cp; 

如果 cp 指向的字符串数组没有null 字符,则,无法达到预期效果。
直到在内存中找到null 字符,循环才会终止。

 

操纵C风格字符串的标准库函数strlen(s)返回s 长度,不包括结束符 nullstrcmp(s1, s2)s1=s2,返回0; s1>s2,返回正数;  s1<s2,返回负数strcat(s1, s2)将s2 连接到 s1,然后返回 s1strcpy(s1, s2)将s2 复制给 s1,然后返回 s1strncat(s1, s2, n)将s2 的前n个字符连接到 s1 后面,并返回 s1strncpy(s1, s2, n)将s2 的前n个字符复制给 s1, 并返回 s1


 






注意:C风格字符串的操作容易出错。
通常使用标准库类型 string代替此类操作



动态数组

动态数组,不必在编译时知道其长度,可以是在运行时再确定数组长度。
每个程序,执行时都占用一块可用内存,用于存放动态分配的对象,
此内存称为:自由存储区(Free Store)或者 堆(Heap)
C++ 使用 newdelete 来分配和释放 自由存储区。

[cpp] view plaincopyprint?
  1. string *sp = new string[10]; 
  2. int *ip = newint[20]; 
  3.  
  4. size_t n = get_size(); 
  5. int *ip3 = newint[n]; 
  6.  
  7. string *sp2 = new string[30](); 
  8. const int *ip2 =new constint[11](); 
  9.  
  10. // char arr[0] 是错误的, 然而,动态分配时,长度可以是0,并返回非0 的指针,但该指针不能解引用 
  11. char *cp = newchar[0]; 
  12.  
  13. delete [] ip3; 


 


使用动态数组,通常是因为,编译时无法知道数组的准确长度。如:以下场景

[cpp] view plaincopyprint?
  1. const char *noerr ="success"
  2. const char *err188 ="Error Code: 188"
  3.  
  4. const char *errorText; 
  5. if(errorFound) 
  6.     errorText = err188; 
  7. else 
  8.     errorText = noerr; 
  9.  
  10. int dimension = strlen(errorText) + 1; 
  11. char *errMsg = new char[dimension]; 
  12. strncpy(errMsg, errorText, dimension); 



 

混合使用标准库类 string 和 C风格字符串
c_str () :返回C风格字符串的表示方法

[cpp] view plaincopyprint?
  1. string str("Hello"); 
  2. const char *cp = str.c_str(); // C 风格字符串自动转换为,指向字符数组首地址的指针 
  3. // char *cp = str.c_str(); 错误,因为c_str() 返回的是 const char 类型的数组 
  4. // char *cp = str;   错误,无法使用 string 对象来初始化 字符指针 
  5.  
  6. // str.c_str() 返回的数组未必永远有效。因为 str 有可能被修改。 
  7. // 因此,最好是复制 str.c_str() 返回的数组 


[cpp] view plaincopyprint?
  1. string s("abcdefg");     
  2. // char *a; 错误 
  3. // char *a = "x";  
  4. // 错误,"x"的内存是在栈上分配的,栈上的内存无法修改 
  5. // 返回的其实是 const char *,而不是 char *, 因此strcpy 将会无法写入,报错 
  6. char *a = newchar[s.length()+1]; 
  7. strcpy(a, s.c_str()); 



 



使用数组初始化 vector

[cpp] view plaincopyprint?
  1. const size_t array_size = 5; 
  2. int int_arr[array_size] = {88, 32, 13, 34, 65}; 
  3.  
  4. // 实际复制了 int_arr[1], int_arr[2], int_arr[3], int_arr[4] 
  5. vector<int> ivec(int_arr + 1, int_arr + array_size); 



 

多维数组,即:数组的数组

[cpp] view plaincopyprint?
  1. int arr_first[2][3] = { {1, 2, 3}, {4, 5, 6} }; 
  2.  
  3. // 与上式等价 
  4. int arr_second[2][3] = {1, 2, 3, 4, 5, 6}; 
  5.  
  6. // 等价于:{ {1, 0, 0}, {2, 0, 0} } 
  7. int arr_third[2][3] = { {1}, {2} }; 
  8.  
  9. // 等价于:{ {1, 2, 0}, {0, 0, 0} } 
  10. int arr_fourth[2][3] = { 1, 2 }; 
  11.  
  12. // ip 是数组,长度为3,数组元素是,指向int 的指针 
  13. int *ip[3];  
  14.  
  15. // ip_second 是指针,指向一个长度为3 的int 数组 
  16. int (*ip_second)[3]; 
  17.  
  18.  
  19. typedef int int_array [3]; 
  20. for(int_array *p_arr = arr_first; p_arr!=arr_first+2; ++p_arr) 
  21.     for(int *p_int = *p_arr; p_int!=*p_arr+3; ++p_int) 
  22.         *p_int = 100; 
  23. int c = arr_first[0][1];