C++primer第四章 数组和指针

4.1. 数组

  数组是由类型名、标识符和维数组成的复合数据类型(第 2.5 节),类型名规定了存放在数组中的元素的类型,而维数则指定数组中包含的元素个数。

  数组定义中的类型名可以是内置数据类型或类类型;除引用之外,数组元素的类型还可以是任意的复合类型。没有所有元素都是引用的数组。

4.1.1. 数组的定义和初始化

  数组的维数必须用值大于等于 1 的常量表达式定义(第 2.7 节)。

  此常量表达式只能包含整型字面值常量、枚举常量(第 2.7 节)或者用常量表达式初始化的整型 const 对象。

// both buf_size and max_files are const
const unsigned buf_size = 512, max_files = 20;
int staff_size = 27; // nonconst
const unsigned sz = get_size(); // const value not known until run time
char input_buffer[buf_size]; // ok: const variable
string fileTable[max_files + 1]; // ok: constant expression
double salaries[staff_size]; // error: non const variable
int test_scores[get_size()]; // error: non const expression
int vals[sz]; // error: size not knownuntil run time

显式初始化数组元素

  在定义数组时,可为其元素提供一组用逗号分隔的初值,这些初值用花括号{}括起来,称为初始化列表:

const unsigned array_size = 3;
int ia[array_size] = {0, 1, 2};

  如果没有显式提供元素初值,则数组元素会像普通变量一样初始化(第 2.3.4节):
  • 在函数体外定义的内置数组,其元素均初始化为 0。
  • 在函数体内定义的内置数组,其元素无初始化。
  • 不管数组在哪里定义,如果其元素为类类型,则自动调用该类的默认构造
  函数进行初始化;如果该类没有默认构造函数,则必须为该数组的元素提供显式初始化。

  显式初始化的数组不需要指定数组的维数值,编译器会根据列出的元素个数来确定数组的长度:

int ia[] = {0, 1, 2}; // an array of dimension 3

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

const unsigned array_size = 5;
// Equivalent to ia = {0, 1, 2, 0, 0}
// ia[3] and ia[4] default initialized to 0
int ia[array_size] = {0, 1, 2};
// Equivalent to str_arr = {"hi", "bye", "", "", ""}
// str_arr[2] through str_arr[4] default initialized to the empty string
string str_arr[array_size] = {"hi", "bye"};

特殊的字符数组

  字符数组既可以用一组由花括号括起来、逗号隔开的字符字面值进行初始化,也可以用一个字符串字面值进行初始化。

char ca1[] = {‘C‘, ‘+‘, ‘+‘}; // no null
char ca2[] = {‘C‘, ‘+‘, ‘+‘, ‘\0‘}; // explicit null
char ca3[] = "C++"; // null terminator added automatically

  不允许数组直接复制和赋值

int ia[] = {0, 1, 2}; // ok: array of ints
int ia2[](ia); // error: cannot initialize one arraywith another
int main()
{
    const unsigned array_size = 3;
    int ia3[array_size]; // ok: but elements are uninitialized!
    ia3 = ia; // error: cannot assign one array toanother
    return 0;
}

4.1.2. 数组操作

  

int main()
{
    const size_t array_size = 10;
    int ia[array_size]; // 10 ints, elements are uninitialized
    // loop through array, assigning value of its index to each element
    for (size_t ix = 0; ix != array_size; ++ix)
        ia[ix] = ix;
    return 0;
}        

使用类似的循环,可以实现把一个数组复制给另一个数组?

int main()
{
    const size_t array_size = 7;
    int ia1[] = { 0, 1, 2, 3, 4, 5, 6 };
    int ia2[array_size]; // local array, elementsuninitialized
    // copy elements from ia1 into ia2
    for (size_t ix = 0; ix != array_size; ++ix)
        ia2[ix] = ia1[ix];
    return 0;
}

检查数组下标值

  导致安全问题的最常见原因是所谓“缓冲区溢出(buffer overflow)”错误。当我们在编程时没有检查下标,并且引用了越出数组或其他类似数据结构边界的元素时,就会导致这类错误。

4.2. 指针的引入

4.2.1. 什么是指针

  指针的概念很简单:指针用于指向对象。

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

string s("hello world");
string *sp = &s; // sp holds the address of s

      

  第二条语句定义了一个指向 string 类型的指针 sp,并初始化 sp 使其指向 string 类型的对象s。*sp 中的 * 操作符表明 sp 是一个指针变量,&s 中的 & 符号是取地址操作符,当此操作符用于一个对象上时,返回的是该对象的存储地址。取地址操作符只能用于左值(第 2.3.1 节),因为只有当变量用作左值时,才能取其地址。同样地,由于用于 vector 类型、string 类型或内置数组的下标操作和解引用操作生成左值,因此可对这两种操作的结果做取地址操作,这样即可获取某一特定对象的存储地址。

4.2.2. 指针的定义和初始化

指针变量的定义

  C++ 语言使用 * 符号把一个标识符声明为指针:

vector<int> *pvec; // pvec can point to a vector<int>
int *ip1, *ip2; // ip1 and ip2 can point to an int
string *pstring; // pstring can point to a string
double *dp; // dp can point to a double

另一种声明指针的风格

string* ps; // legal but can be misleading

连续声明多个指针易导致混淆

string* ps1,ps2;

指针可能的取值

  一个有效的指针必然是以下三种状态之一:

  • 保存一个特定对象的地址;
  • 指向某个对象后面的另一对象;
  • 或者是0 值。
int ival = 1024;
int *pi = 0; // pi initialized to address no object
int *pi2 = & ival; // pi2 initialized to address of ival
int *pi3; // ok, but dangerous, pi3 is uninitialized
pi = pi2; // pi and pi2 address the same object, e.g.ival
pi2 = 0; // pi2 now addresses no object

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

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

1. 0 值常量表达式(第 2.7 节),例如,在编译时可获得 0 值的整型 const对象或字面值常量 0。
2. 类型匹配的对象的地址。
3. 另一对象末的下一地址。
4. 同类型的另一个有效指针

  除了使用数值0 或在编译时值为 0 的 const 量外,还可以使用 C++ 语言从 C 语言中继承下来的预处理器变量 NULL(第 2.9.2 节),该变量在 cstdlib头文件中定义,其值为 0。如果在代码中使用了这个预处理器变量,则编译时会自动被数值 0 替换。因此,把指针初始化为 NULL 等效于初始化为 0 值:

// cstdlib #defines NULL to 0
int *pi = NULL; // ok: equivalent to int *pi = 0;

  指针只能初始化或赋值为同类型的变量地址或另一指

double dval;
double *pd = &dval; // ok: initializer is address of a double
double *pd2 = pd; // ok: initializer is a pointer to double
int *pi = pd; // error: types of pi and pd differ
pi = &dval; // error: attempt to assign address of a doubleto int *

void* 指针

C++ 提供了一种特殊的指针类型 void*,它可以保存任何类型对象的地址:

double obj = 3.14;
double *pd = &obj;
// ok: void* can hold the address value of any data pointer type
void *pv = &obj; // obj can be an object of any type
pv = pd; // pd can be a pointer to any type

4.2.3. 指针操作

  与对迭代器进行解引用操作(第 3.4节)一样,对指针进行解引用可访问它所指的对象,* 操作符(解引用操作符)将获取指针所指的对象:

string s("hello world");
string *sp = &s; // sp holds the address of s
cout <<*sp; // prints hello world

生成左值的解引用操作

  解引用操作符返回指定对象的左值,利用这个功能可修改指针所指对象的值:

*sp = "goodbye"; // contents of s now changed

指针和引用的比较

    第一个区别在于引用总是指向某个对象:定义引用时没有初始化是错误的。

    第二个重要区别则是赋值行为的差异:给引用赋值修改的是该引用所关联的对象的值,而并不是使引用与另一个对象关联。

    考虑以下两个程序段。第一个程序段将一个指针赋给另一指针:

int ival = 1024, ival2 = 2048;
int *pi = &ival, *pi2 = &ival2;
pi = pi2; // pi now points to ival2

  赋值结束后,pi 所指向的 ival 对象值保持不变,赋值操作修改了 pi 指针的值,使其指向另一个不同的对象。

  现在考虑另一段相似的程序,使用两个引用赋值:

int &ri = ival, &ri2 = ival2;
ri = ri2; // assigns ival2 to ival

这个赋值操作修改了 ri 引用的值 ival 对象,而并非引用本身。赋值后,这两个引用还是分别指向原来关联的对象,此时这两个对象的值相等。

指向指针的指针

指针本身也是可用指针指向的内存对象。指针占用内存空间存放其值,因此指针的存储地址可存放在指针中。

int ival = 1024;
int *pi = &ival; // pi points to an int
int **ppi = &pi; // ppi points to a pointer to int

定义了指向指针的指针。C++ 使用 ** 操作符指派一个指针指向另一指针。
这些对象可表示为:

为了真正地访问到 ival 对象,必须对 ppi 进行两次解引用:

cout << "The value of ival\n"
    << "direct value: " << ival << "\n"
    << "indirect value: " << *pi << "\n"
    << "doubly indirect value: " << **ppi
    << endl;

这段程序用三种不同的方式输出 ival 的值。首先,采用直接引用变量的方式输出;然后使用指向 int 型对象的指针 pi 输出;最后,通过对 ppi 进行两次解引用获得 ival 的特定值。

4.2.4. 使用指针访问数组元素

  int ia[] = {0,2,4,6,8};
  int *ip = ia; // ip points to ia[0]

如果希望使指针指向数组中的另一个元素,则可使用下标操作符给某个元素定位,然后用取地址操作符 & 获取该元素的存储地址:

    ip = &ia[4]; // ip points to last element in ia

指针的算术操作

ip = ia; // ok: ip points to ia[0]
int *ip2 = ip + 4; // ok: ip2 points to ia[4], the last elementin ia

只要两个指针指向同一数组或有一个指向该数组末端的下一单元,C++ 还支持对这两个指针做减法操作:

ptrdiff_t n = ip2 - ip; // ok: distance between the pointers

解引用和指针算术操作之间的相互作用

  在指针上加一个整型数值,其结果仍然是指针。允许在这个结果上直接进行解引用操作,而不必先把它赋给一个新指针:

int last = *(ia + 4); // ok: initializes last to 8, the valueof ia[4]

加法操作两边用圆括号括起来是必要的。如果写为:

last = *ia + 4; // ok: last = 4, equivalent to ia[0]+4

意味着对 ia 进行解引用,获得 ia 所指元素的值 ia[0],然后加 4。

下标和指针

  在表达式中使用数组名时,实际上使用的是指向数组第一个元素的指针。

int ia[] = {0,2,4,6,8};
int i = ia[0]; // ia points to the first element in ia
int *p = &ia[2]; // ok: p points to the element indexed by2
int j = p[1]; // ok: p[1] equivalent to *(p + 1),
// p[1] is the same element as ia[3]
int k = p[-2]; // ok: p[-2] is the same element as ia[0]

在使用下标访问数组时,实际上是对指向数组元素的指针做下标操作。

计算数组的超出末端指针

  可以计算数组的超出末端指针的值:

const size_t arr_size = 5;
int arr[arr_size] = {1,2,3,4,5};
int *p = arr; // ok: p points to arr[0]
int *p2 = p + arr_size; // ok: p2 points one past the end ofarr
// use caution -- do not
dereference!

输出数组元素

const size_t arr_sz = 5;
int int_arr[arr_sz] = { 0, 1, 2, 3, 4 };
// pbegin points to first element, pend points just after thelast
for (int *pbegin = int_arr, *pend = int_arr + arr_sz;
pbegin != pend; ++pbegin)
cout << *pbegin << ‘ ‘; // print the current element

4.2.5. 指针和const 限定符

指向const 对象的指针

  到目前为止,我们使用指针来修改其所指对象的值。但是如果指针指向const 对象,则不允许用指针来改变其所指的 const 值。为了保证这个特性,C++ 语言强制要求指向 const 对象的指针也必须具有 const 特性:

const double *cptr; // cptr may point to a double that is const

  把一个 const 对象的地址赋给一个普通的、非 const 对象的指针也会导致编译时的错误:

const double pi = 3.14;
double *ptr = &pi; // error: ptr is a plain pointer
const double *cptr = &pi; // ok: cptr is a pointer to const

  不能使用 void* 指针(第 4.2.2 节)保存 const 对象的地址,而必须使用 const void* 类型的指针保存 const 对象的地址:

const int universe = 42;
const void *cpv = &universe; // ok: cpv is const
void *pv = &universe; // error: universe is const

  允许把非 const 对象的地址赋给指向 const 对象的指针,例如:

double dval = 3.14; // dval is a double; its value can be changed
cptr = &dval; // ok: but can‘t change dval through cptr

******************注意**************************

dval = 3.14159; // dval is not const
*cptr = 3.14159; // error: cptr is a pointer to const
double *ptr = &dval; // ok: ptr points at non-const double
*ptr = 2.72; // ok: ptr is plain pointer
cout << *cptr; // ok: prints 2.72

  如果指向 const 的指针所指的对象并非 const,则可直接给该对象赋值或间接地利用普通的非 const 指针修改其值:毕竟这个值不是 const。重要的是要记住:不能保证指向 const 的指针所指对象的值一定不可修改。

const 指针

  与上边的指向const数据的指针对比,找区别

int errNumb = 0;
int *const curErr = &errNumb; // curErr is a constant pointer

“curErr 是指向 int 型对象的const 指针”。

指向const 对象的 const 指针

  这段代码什么意思?

const double pi = 3.14159;
// pi_ptr is const and points to a const object
const double *const pi_ptr = &pi;

指针和 typedef(太晕了,暂时不讲了)

 4.3. C 风格字符串

  现在可以更明确地认识到:字符串字面值的类型就是const char 类型的数组。

char ca1[] = {‘C‘, ‘+‘, ‘+‘}; // no null, not C-style string
char ca2[] = {‘C‘, ‘+‘, ‘+‘, ‘\0‘}; // explicit null
char ca3[] = "C++"; // null terminator added automatically
const char *cp = "C++"; // null terminator added automatically
char *cp1 = ca1; // points to first element of a array, but not C-style string
char *cp2 = ca2; // points to first element of a null-terminated char array

C 风格字符串的使用

const char *cp = "some value";
while (*cp) {
// do something to *cp
++cp;
}

C 风格字符串的标准库函数

  cstring 是 string.h 头文件的 C++ 版本,而 string.h 则是 C 语言提供的标准库。

永远不要忘记字符串结束符 null

char ca[] = {‘C‘, ‘+‘, ‘+‘}; // not null-terminated
cout << strlen(ca) << endl; // disaster: ca isn‘t null-terminated

调用者必须确保目标字符串具有足够的大小

  传递给标准库函数 strcat 和 strcpy 的第一个实参数组必须具有足够大的空间存放新生成的字符串。以下代码虽然演示了一种通常的用法,但是却有潜在的严重错误:

// Dangerous: What happens if we miscalculate the size of largeStr
char largeStr[16 + 18 + 2]; // will hold cp1 a spaceand cp2
strcpy(largeStr, cp1); // copies cp1 into largeStr
strcat(largeStr, " "); // adds a space at end of largeStr
strcat(largeStr, cp2); // concatenates cp2 to largeStr
// prints A string example A different string
cout << largeStr << endl;

使用strn 函数处理 C 风格字符串

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

  • 调用 strncpy 时,要求复制 17 个字符:字符串 cp1 中所有字符,加上结束符 null。留下存储结束符 null 的空间是必要的,这样 largeStr 才可以正确地结束。调用 strncpy 后,字符串 largeStr 的长度 strlen 值是 16。记住:标准库函数 strlen 用于计算 C 风格字符串中的字符个数,不包括 null 结束符。
  • 调用 strncat 时,要求复制 2 个字符:一个空格和结束该字符串字面值的 null。调用结束后,字符串 largeStr 的长度是 17,原来用于结束largeStr 的 null 被新添加的空格覆盖了,然后在空格后面写入新的结束符 null。

  • 第二次调用 strncat 串接 cp2 时,要求复制 cp2 中所有字符,包括字符串结束符 null。调用结束后,字符串 largeStr 的长度是 35:cp1 的16 个字符和 cp2 的 18 个字符,再加上分隔这两个字符串的一个空格。

尽可能使用标准库类型string

string largeStr = cp1; // initialize large Str as a copy of cp1
largeStr += " "; // add space at end of largeStr
largeStr += cp2; // concatenate cp2 onto end of largeStr

4.3.1. 创建动态数组

4.4. 多维数组 

// array of size 3, each element is an array of ints of size 4
int ia[3][4];

多维数组的初始化

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 */
};
// equivalent initialization without the optional nested braces foreach row
int ia[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};

多维数组的下标引用

const size_t rowSize = 3;
const size_t colSize = 4;
int ia [rowSize][colSize]; // 12 uninitialized elements
// for each row
for (size_t i = 0; i != rowSize; ++i)
// for each column within the row
for (size_t j = 0; j != colSize; ++j)
// initialize to its positional index
ia[i][j] = i * colSize + j;

4.4.1. 指针和多维数组

int ia[3][4]; // array of size 3, each element is an array of
ints of size 4
int (*ip)[4] = ia; // ip points to an array of 4 ints
ip = &ia[2]; // ia[2] is an array of 4 ints

用 typedef 简化指向多维数组的指针

typedef int int_array[4];
int_array *ip = ia;

可使用 typedef 类型输出 ia 的元素

for (int_array *p = ia; p != ia + 3; ++p)
for (int *q = *p; q != *p + 4; ++q)
cout << *q << endl;

小结
  本章介绍了数组和指针。数组和指针所提供的功能类似于标准库的 vector类与 string 类和相关的迭代器所提供。我们可以把 vector 类型理解为更灵活、更容易管理的数组,同样,string 是 C 风格字符串的改进类型,而 C 风格字符串是以空字符结束的字符数组。

  迭代器和指针都能用于间接地访问所指向的对象。vector 类型所包含的元素通过迭代器来操纵,类似地,指针则用于访问数组元素。尽管道理都很简单,但在实际应用中,指针的难用是出了名的。

  某些低级任务必须使用指针和数组,但由于使用指针和数组容易出错而且难以调试,应尽量避免使用。一般而言,应该优先使用标准库抽象类而少用语言内置的低级数组和指针。尤其是应该使用 string 类型取代 C 风格以空字符结束的字符数组。现代 C++ 程序不应使用C 风格字符串。

时间: 2024-10-25 12:47:29

C++primer第四章 数组和指针的相关文章

第四章 数组,字符串和指针

数组 数组若没有指定初始值则为内存遗留值 如果指定了部分初始值,那么其余部分也默认被指定为0: long data[100]={0};          //给data数组的所有元素赋0 字符串是附加有特殊字符(/0)的字符序列 数组的填充: char president[]="thank you"; wchar_t president[]=L"thank you";     //Unicode字符串 const int max=80; char name[max]

《C++ Primer 4th》读书笔记 第4章-数组和指针

原创文章,转载请注明出处: http://www.cnblogs.com/DayByDay/p/3911573.html <C++ Primer 4th>读书笔记 第4章-数组和指针

第十四章 数组

第十四章 数组 1.  什么是数组 三要素:数组类型  数组名  下标 2.  数组元素 3.  数组下标越界 一旦越界程序就会报错 4.  倒序输出 5.  将数组的下标定义为常量以便于修改 6.  手动操作数组元素 7.  数组的初始化 空间不够报错,多了用0补 ① ② 字符数组的初始化 char array[10]={"hello"} 等价于char array[10]= "hello" char array[10]={'h','e','l','l','o',

第四章 数组、关联数组和别名使用

第四章 数组.关联数组和别名使用 数组 名词解释 数组作为一种特殊的数据结构在任何一种编程语言中都有它的一席之地,数组在shell脚本中也是非常重要的组成部分,它借助索引将多个独立的数据存储为一个角色. 语法 普通数组只能使用整数作为数组的索引值. 定义数组 格式: arrary[key]=value array=(value value value ...) 数组方法 单行一列值: array_n=(1 2 3 4) 打印数组第一个值: echo ${array_n[0]} # 0 代表数组里

【C语言学习】《C Primer Plus》第10章 数组和指针

学习总结 1.数组初始化方式: int a[]={1,2,3} int a[SIZE]={1,2,3} //SIZE是宏定义,数组初始化个数不能大于SIZE,否则报错:当个数小 //SIZE,自动补0:只定义不初始化,默认值是当前存储单元中已有的数值. int a[SIZE/不定长]={1,[3],2} //C99支持通过[int]=x来定义具体位置值,跳过的默认值为0. 2.通过const修饰的数组为只读数组,数组的每个元素当成常量来处理,如: const int a[2]={1,2}; co

小猪猪逆袭成博士之C++基础篇(四)数组、指针、vector、迭代器

小猪猪逆袭成博士之C++基础篇(四) 关键词:数组,Vector. 一.数组与指针 数组相信大家学过C语言或者其他的语言都不陌生,简单的就是同一个变量类型的一组数据.例如:int a[10],意思就是从a开始有10个连续的int大小的空间.我们还是从初始化说起. 我们以数据类型int为例,当然也可由有很多的数据类型,可以是像int,double这种,也可以是自定义的类,一般的初始化方法有: int a[10]; int a[10]={0}; int a[3]={0,1,2}; 在前面的文章中,有

第一章 数组与指针概念剖析

数组与指针生来就是双胞胎,多数人就是从数组的学习开始指针的旅程的.在学习的过程中,很自然就会经常听到或见到关于数组与指针的各种各样的看法,下面我节选一些在各种论坛和文章里经常见到的文字: “一维数组是一级指针” “二维数组是二级指针” “数组名是一个常量指针” “数组名是一个指针常量” ........................ 这些文字看起来非常熟悉吧?类似的文字还有许多.不过非常遗憾,这些文字都是错误的,实际上数组名永远都不是指针!这个结论也许会让你震惊,但它的确是事实.但是,在论述这

C++ Primer 随笔 Chapter 4 数组和指针

1.数组:数组是由类型名.标识符和维数组成的符合数据类型,类型名规定了存放在数组中的元素类型,维数规定数组中包含元素的个数而标识符就是数组的名称.例如: int  arr[10]; 其中 int 是类型名,arr是标识符而 10 是数组的维数. 2.数组的定义和初始化:数组的初始化要注意以下几点 (1). 数组中存放的元素类型不能是引用类型,除此之外可以是其他任何类型. (2). 数组的维数必须是字面值常量.枚举常量或者常量表达式(但不能是在运行时才知道其值得常量表达式) (4). 显示初始化整

C++Primer 第四章

//1.当我们对运算符进行重载的时候,其包括运算对象的类型和返回值的类型都是由该运算符定义的,但是运算对象的个数和优先级,结合律都是不能改变的 //2.当一个对象被用作右值的时候,用的是对象的值(内容).当对象被用作左值的时候,用的是对象的身份(在内存中的位置). //3.复合表达式:是指含有两个或多个运算符的表达式.求复合表达式的值需要首先将运算符和运算对象合理的组合在一起.优先级和结合律决定了运算对象的组合方式. // 括号无视普通的组合规则,在表达式中括号括起来的部分被当做一个单元来求值,