06_Pointer_String

本章主要分享指针与字符串( 为什么指针、持久动态内存分配 、字符串(字符数组))。 指针 {什么是指针 指针的定义与运算 指针与一维(二维)数组 指针数组 指针与引用 指针与函数 } 持久动态存储分配 {动态内存申请 --- new 动态内存释放 --- delete 动态数组的申请与释放 在被调函数中申请动态内存, 返回给主调函数 } 字符串(字符数组) {字符串的表示 字符串输入输出 字符串操作 --- 相关函数 字符操作函数 }
展开查看详情

1.第六讲 指针与字符串 —— 为什么 指针 —— 持久动态内存分配 —— 字符串 ( 字符数组 )

2.2 指 针 什么是指针 指针的定义与运算 指针与一 维(二维)数组 指针数组 指针与引用 指针与函数

3.3 指针定义 什么是指针 指针变量,简称指针,用来存放其它变量的 内存地址 指针的定义 类型标识符 * 指针变量名 声明一个指针类型的变量,星号后面可以留空格 类型标识符 表示该指针 所指向的对象 的数据类型,即该指针所代表的内存单元所能存放的数据的类型 Tips : 变量为什么要声明? 1) 分配内存空间 ; 2) 限定变量能参与的运算及运算规则。 Tips : 内存空间的访问方式: 1) 变量名 ; 2) 内存地址,即指针。

4.4 指针运算 指针的两个基本运算 提取变量的内存地址: & 提取指针所指向的变量的值: * 地址运算符: & & 变量名 // 提取变量在内存中的存放地址 此时,我们通常称这里的 px 是指向 x 的指针 注意:指针的类型必须与其指向的对象的类型一致 例: int x=3; int * px; // 定义指针 px px=&x; // 将 x 的地址赋给指针 px

5.5 指针运算 指针运算符: * 指针变量名 // 提取指针变量所指向的对象的值 例: int x; int * px; // 声明指针变量,星号后面可以有空格! px=&x; *px = 3; // 等价于 x = 3 ,星号后面不能有空格! ex06_pointer_01.cpp * 在使用指针时,我们通常关心的是指针指向的元素! 初始化:声明指针变量时,可以赋初值 例: int x = 3; int * px = &x; // 指针的值只能是某个变量的地址

6.6 空指针 void 类型的指针 void * 指针名 void 类型的指针可以指向任何类型的对象的地址 不允许使用 void 指针操纵它所指向的对象! 通过 显式类型转换 ,可以访问 void 类型指针所指向的对象。 int x = 3; int * px ; void * pv ; pv = &x; // OK, void 型指针指向整型变量 px = ( int *) pv ; // OK ,使用 void 型指针时需要强制类型转换 例:

7.7 指针赋值 指针可能的取值 一个有效的指针只有三种取值: (1) 一个对象的地址; (2) 指向某个对象后面的对象; (3) 值为 0 或 NULL (空指针)。 没有 初始化 或 赋值 的指针是 无效的指针 , 引用无效指针会带来难以预料的问题! 指针赋值:只能使用以下四种类型的值 (1) 0 或者值为 0 的常量,表示空指针; (2) 类型匹配的对象的地址; (3) 同类型的另一有效指针; (4) 另一个对象的下一个 地址(相对位置)。 int x=3; int * px =&x; Int * py =&x+1; int * pi; pi=0; // OK pi=NULL; // OK

8.8 指针与常量 指向常量的指针 const int a = 3; int * pa = &a; // ERROR const int * cpa = &a; // OK 指向 const 对象(常量)的指针必须用 const 声明! 这里的 const 限定了指针所指对象的属性,不是指针本身的属性! const int a = 3; int b = 5; const int * cpa = &a; // OK *cpa = 5; // ERROR cpa = &b; // OK *cpa = 9; // ERROR b = 9; // OK 允许把非 const 对象的地址赋给 指向 const 的指针 ; 但不允许使用 指向 const 的指针 来修改它所指向的对象的值! 指向 const 的指针所指对象的值并 不一定不能修改 ! ex06_pointer_const.cpp const 类型标识符 * 指针名

9.9 指针与常量 常量指针,简称常指针 int a = 3, b = 5; int * const pa = &a; // OK pa = &b; // ERROR 常量指针: 指针本身的值不能修改 类型标识符 * const 指针名 指向 const 对象的 const 指针 const 类型标识符 * const 指针名 指针本身的值不能修改,其指向的对象的值也不能修改

10.10 指针算术运算 int * pa; int k ; pa + k --> pa 所指的当前位置之后第 k 个元素的地址 pa - k --> pa 所指的当前位置之前第 k 个元素的地址 指针可以和整数或整型变量进行加减运算, 且运算规则与指针的类型相关! 在指针上加上或减去一个整型数值 k ,等效于获得一个新指针,该指针指向原来的元素之后或之前的第 k 个元素 指针的算术运算通常是与 数组 的使用相联系的 一个指针可以加上或减去 0 ,其值不变 int * pa; int k ; pa++ --> pa 所指的当前位置之后的元素的地址 pa-- --> pa 所指的当前位置之前的元素的地址

11.11 指针算术运算 pa pa-2 pa-1 pa+1 pa+2 pa+3 * (pa-2) * pa * (pa+1) * (pa+2) * (pa+3) * (pa-1) short * pa // 每个元素占两个字节

12.12 指针算术运算 pb pb-1 pb+1 pb+2 * pb * (pb+1) * (pb+2) * (pb-1) int * pb // 每个元素占四个字节

13.13 指针与数组 int a[]={0,2,4,8}; int * pa; pa = a ; // OK pa = &a[0] ; // OK, 与上式等价 *pa = 3 ; // OK ,等价于 a[0]=3 *(pa+2) = 5 ; // OK ,等价于 a[2]=5 *(a+2) = 5 ; // OK ,等价于 a[2]=5 C++ 中,指针与数组密切相关:由于数组元素在内存中是连续存放的,因此使用指针可以非常方便地处理数组元素! 思考: pa = a+1 ,则 * pa = ? 在 C++ 中,数组名就是数组的首地址! 当数组名出现在表达式中时,会自动转化成指向第一个数组元素的指针!

14.14 一维数组与指针 int a[]={0,2,4,8}; int * pa = a; *pa = 1 ; // 等价于 a[0]=1 *(pa+2) = 5 ; // 等价于 a[2]=5 *(a+2) = 5 ; // OK ,等价于 a[2]=5 *(pa++) = 3 ; // OK ,等价于 a[0]=3; pa = pa+1; *(a++) = 3 ; // ERROR! a 代表数组首地址,是常量指针! * (pa+1) = 10; // 思考:修改了哪个元素的值? 一维 数组 与 指针 在 C++ 中,引用数组元素有以下三种方式: (1) 数组名与下标, 如: a[0] (2) 数组名与指针运算, 如:* (a+1) (3) 指针, 如: int * pa=a; *pa 指针的值可以随时改变,即可以指向不同的元素; 数组名是常量指针,值不能改变。 ex06_pointer_array01.cpp

15.15 举例 例: 使用三种方法输出一个数组的所有元素 // 第一种方式:数组名与下标 for (int i=0; i<n; i++) cout << a[i] << "," ; // 第二种方式:数组名与指针运算 for (int i=0; i<n; i++) cout << *(a+i) << "," ; // 第三种方式:指针 for (int * pa=a; pa<a+n; pa++) cout << *pa << "," ; // 第三种方式:指针 for (int * pa=a, i=0; i<n; i++) cout << pa[i] << "," ; 若 pa 是指针, k 是整型数值,则 * (pa+k) 可以写成 pa[k] * (pa-k) 可以写成 pa[-k] ex06_pointer_array02.cpp

16.16 一维数组与指针 一维数组 a[n] 与指针 pa=a 数组名 a 是 地址常量 ,数组名 a 与 &a[0] 等价; a+i 是 a[i] 的地址, a[i] 与 *(a+i) 等价; 数组元素的下标访问方式也是按地址进行的; 可以通过指针 pa 访问数组的任何元素,且更加灵活; pa++ 或 ++pa 合法,但 a++ 不合法; *(pa+i) 与 pa[i] 等价,表示第 i+1 的元素; a[ i ] <=> pa[ i ] <=> *( pa+i ) <=> *( a+i )

17.17 指针数组 int a[]={0,2,4,8}; int b[]={1,3,5,7}; int c[]={2,3,5,8}; int *pa[3]={a,b,c}; // 声明一个有三个元素的指针数组 // pa[0]=a, pa[1]=b, pa[2]=c 指针数组 :数组的元素都是指针变量 指针数组的声明: 类型标识符 * 指针数组名 [n] 上面的 pa 代表指针数组,不是普通的指针!

18.18 二维数组 例: int A[2][3]={{1,2,3},{7,8,9}}; C++ 中,二维数组是按行顺序存放在内存中的,可以理解为一维数组组成的一维数组。 A[0] —— A 00 A 01 A 02 A[1] —— A 10 A 11 A 12 A 可以理解为: A[0][0] A[0][1] A[0][2] A[1][0] A[1][1] A[1][2] A[0] A[1] (第一行的首地址) (第二行的首地址) A[0], A[1] 称为 行数组

19.19 二维数组与指针 int A[2][3]={{1,2,3},{7,8,9}}; int * pa = A[0]; // 行数组 A[0] 的首地址 , 等价于 pa=&A[0][0] int * pa1 = A[1]; // 行数组 A[1] 的首地址,即 &A[1][0] *pa = 11; // 等价于 A[0][0]=11 *(pa+2) = 12; // 等价于 A[0][2]=12 *(pa+4) = 13; // 等价于 A[1][1]=13

20.20 二维数组与指针 对于二维数组 A ,虽然 A 、 A[0] 都是数组首地址,但二者指向的对象不同: 而 A 是一个 二维数组 的名字,它指向的是它的首元素,即 A[0] A[0] 是 一维数组 的名字,它指向的是行数组 A[0] 的首元素,即 A[0][0] * A[0] A[0][0] * (A[0]+1) A[0][1] * A A[0] * (A+1) A[1]

21.21 二维数组与指针 int A[2][3]={{1,2,3},{7,8,9}}; int * p = A; // ERROR ! int * p = A[0]; // OK 当 int * p 声明指针时, p 指向的是一个 int 型数据,而不是一个地址,因此,用 A[0] 对 p 赋值是正确的,而用 A 对 p 赋值是错误的! 设指针 p=&A[0][0] ,则 A[ i ][j] <==> *( p+n * i+j ) 如何用普通指针引用二维数组的元素? ex06_pointer_array2D.cpp 例: 二维数组与普通指针

22.22 指针与引用 引用是变量的别名; 引用必须初始化,且不能修改; 引用只针对变量,函数没有引用; 传递大量数据时,最好使用指针; 用引用能实现的功能,用指针都能实现。 引用作为函数参数的优点 传递方式与指针类似,但可读性强; 函数调用比指针更简单、安全; 引用与指针 int a = 3; int * pa = &a; // 指针 int & ra = a; // 引用

23.23 指针作为函数参数 以地址方式传递数据。 形参是指针时,实参可以是指针或地址。 指针作为函数参数 当函数间需要传递大量数据时,开销会很大。此时,如果数据是 连续存放 的,则可以只传递数据的 首地址 ,这样就可以减小开销,提高执行效率! void split (double x, int * n, double * f ) double x, x2; int x1; split (x, &x1, &x2 ) ex06_pointer_fun01.cpp

24.24 指针作为函数参数 使形参和实参指向共同的内存地址; 减小函数间数据传递的开销; 传递函数代码的首地址(后面介绍)。 指针作为函数参数 的三个作用 Tips : 如果在被调函数中不需要改变指针所指向的对象的值,则可以将形参中的指针声明为指向常量的指针。

25.25 指针型函数 当函数的返回值是 地址 时,该函数就是 指针 型 函数 指针型函数的定义 数据类型 * 函数名 (形参列表) { 函数体 }

26.26 指向函数的指针 在程序运行过程中,不仅数据要占用内存空间, 函数 也要在内存中占用一定的空间。 函数名就代表函数在内存空间中的首地址 。用来存放这个地址 的 指针就是指向该函数的指针。 函数指针的定义 Tips : 可以象使用函数名一样使用函数指针。 注: 函数名除了表示函数的首地址外,还包括函数的返回值类型,形参个数、类型、顺序等信息。 数据类型 (* 函数指针名) (形参列表) 这里的 数据类型 和形参列表应与其 指向的函数 相同

27.27 函数指针 函数指针需要赋值后才能使用 int Gcd(int x, int y); int Lcm(int x, int y) ; int (* pf)(int, int); // 声明函数指针 pf = Gcd; // pf 指向函数 Gcd cout << " 最大公约数 : " << pf(a,b) << endl; pf = Lcm; // pf 指向函数 Lcm cout << " 最小公倍数 : " << pf(a,b) << endl; ex06_pointer_fun02.cpp

28.28 持久 动态存储分配 动态内存申请 --- new 动态内存释放 --- delete 动态数组的申请与释放 在被调函数中申请动态内存 , 返回给主调函数

29.29 动态存储分配 申请内存空间 : new 释放内存空间 : delete 若在程序运行之前,不能够确切知道数组中元素的个数,如果声明为很大的数组,则可能造成浪费,如果声明为小数组,则可能不够用。此时需要 动态分配 空间,做到按需分配。 动态内存分配相关函数 每个程序在执行时都会占用一块可用的内存,用于存放动态分配的对象,此内存空间称为自由存储区( free store )。