程序设计基础 第 八 章 谌 卫 军 清华大学软件学院 2008年秋季.

Slides:



Advertisements
Similar presentations
第九章 指针 西安工程大学.
Advertisements

第七章 指针 计算机公共教学部.
第7章 指针 存储地址的变量的类型就是指针类型 能直接对内存地址操作, 实现动态存储管理 容易产生副作用, 初学者常会出错
二级指针与二维数组.
C语言程序设计基础 第10章 指针进阶 刘新国.
10.1 二级指针 10.2 指针与二维数组 10.3 指针的动态存储分配 10.4 函数指针 10.5 main函数的参数
補充: Input from a text file
第6章 指针 6.1 指针的概念 6.2 变量与指针 6.3 数组与指针 6.4 字符串与指针 6.5 函数与指针 6.6 返回指针值的函数
6.4 字符串与指针 1. 用字符数组存放一个字符串.
8.1 指针的概念 8.2 指针变量 8.3 指针变量的基础类型 8.4 指针的运算 8.5 指针与一维数组 8.6 指针应用实例
C++中的声音处理 在传统Turbo C环境中,如果想用C语言控制电脑发声,可以用Sound函数。在VC6.6环境中如果想控制电脑发声则采用Beep函数。原型为: Beep(频率,持续时间) , 单位毫秒 暂停程序执行使用Sleep函数 Sleep(持续时间), 单位毫秒 引用这两个函数时,必须包含头文件
第九章 字符串.
Linked List Operations
C语言程序设计 课程 第5章 数组 主讲:李祥 博士、副教授 单位:软件学院软件工程系.
指 针 为什么要使用指针 指针变量 指针与数组 返回指针值的函数 动态内存分配 通过指针引用字符串 指向函数的指针 小 结 习 题.
C 程式設計— 指標.
第 十 章 指 针.
C语言高级编程(第四部分) 字符串 北京大学 信息科学技术学院.
第7章 间接访问—指针 指针的概念 指针运算与数组 动态内存分配 字符串再讨论 指针作为函数参数和返回值 指针数组与多级指针
Chap 8 指针 8.1 寻找保险箱密码 8.2 角色互换 8.3 冒泡排序 8.4 电码加密 8.5 任意个整数求和*
Chap 8 指针 8.1 寻找保险箱密码 8.2 狸猫换太子 8.3 冒泡排序 8.4 加密变换问题 8.5 任意个整数求和问题*
辅导课程六.
Zhao4zhong1 (赵中) C语言指针与汇编语言地址.
Zhao4zhong1 (赵中) C语言指针与汇编语言地址.
二维数组的指针表示 与复杂的指针例子 专题研讨课之三.
第一单元 初识C程序与C程序开发平台搭建 ---观其大略
C语言 程序设计基础与试验 刘新国、2012年秋.
多维数组与指针 用指针变量可以指向一维数组中的元素,也可以指向多维数组中的元素。但在概念上和使用上,多维数组的指针比一维数组的指针要复杂一些。 1. 多维数组元素的地址 先回顾多维数组的性质,可以认为二维数组是“数组的数组”,例 : 定义int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}};
C语言程序设计基础 第8章 指针 刘新国.
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
第8章 指针.
第六章 指针 指针的概念 指针的运算 指向变量的指针 指向数组的指针 指向函数的指针 二级指针 主讲:李祥 时间:2015年10月.
Introduction to the C Programming Language
欲穷千里,更上层楼 第十章 指 针 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; 能很方便地使用数组和字符串; 并能象汇编语言一样处理内存地址,从而编出精练而高效的程序。指针极大地丰富了C语言的功能。 学习指针是学习C语言中最重要的一环,
第五章 习题课 电子信息与计算机科学系 曾庆尚.
第七章 操作符重载 胡昊 南京大学计算机系软件所.
第7章 陣列與指標 7-1 陣列的基礎 7-2 一維陣列的處理 7-3 二維與多維陣列的處理 7-4 陣列的函數參數
第五章 指针 5.1 指针的概念 5.2 指针与数组 5.3 字符串指针.
C++语言程序设计 C++语言程序设计 第七章 类与对象 第十一组 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
本节内容 随机读取 视频提供:昆山爱达人信息技术有限公司.
C语言大学实用教程 第7章 指针 西南财经大学经济信息工程学院 刘家芬
Introduction to the C Programming Language
指针 几个概念:  指针也是一种数据类型,具有指针类型的变量,称为指针变量。
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
第6讲 指针与引用 6.1 指针 6.2 引用.
<编程达人入门课程> 本节内容 内存的使用 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群: ,
C语言程序设计 第一章 数据类型, 运算符与表达式 第二章 顺序程序设计 第三章 选择结构程序设计 第四章 循环控制 第五章 数组.
第4章 Excel电子表格制作软件 4.4 函数(一).
本节内容 Private Memory 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
第九节 赋值运算符和赋值表达式.
3.16 枚举算法及其程序实现 ——数组的作用.
本节内容 结构体 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
第五章 数组与指针 1 数组的简单操作 2 数组与指针 3 字符数组与字符串 4 字符串处理函数 5 数组应用举例.
多层循环 Private Sub Command1_Click() Dim i As Integer, j As Integer
C++语言程序设计 C++语言程序设计 第六章 指针和引用 第十一组 C++语言程序设计.
C程序设计.
2.6 字符型数据 一、 字符常量 1、字符常量的定义 用一对单引号括起来的单个字符,称为字符常量。 例如,‘A’、‘1’、‘+’等。
本节内容 C语言的汇编表示 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
本节内容 结构体.
本节内容 指针类型的使用 视频提供:昆山爱达人信息技术有限公司.
C++语言程序设计 C++语言程序设计 第一章 C++语言概述 第十一组 C++语言程序设计.
本节内容 动态链接库 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
C语言程序设计 第8章 指针.
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
基本知识 数据类型、变量、常量、运算符.
Introduction to the C Programming Language
第八章 指 针 北京邮电大学出版社.
本节内容 this指针 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ: QQ交流群 : 联系电话:
第7章 地址和指针 7.1 地址和指针的概念 7.2 指针变量的定义和指针变量的基类型 7.3 给指针变量赋值 7.4 对指针变量的操作
Presentation transcript:

程序设计基础 第 八 章 谌 卫 军 清华大学软件学院 2008年秋季

第 八 章 指针

8.1 什么是指针 指针是C语言中最重要的特性之一,要 成为一个好的 C/C++ 程序员,就必须 理解指针的概念,并善于使用它。

8.1.1 地址亦数据 某国情报机关最近一段时期接连损失了好几名 情报人员,经过调查,发现是在自己内部存在一名 8.1.1 地址亦数据 某国情报机关最近一段时期接连损失了好几名 情报人员,经过调查,发现是在自己内部存在一名 “鼹鼠”,但不知是谁。不得已,买通了一名隐藏极 深的双重间谍进行调查。很快,调查取得了重大突 破,这名间谍成功地获取了该“鼹鼠”的代号。因此 情报局长派遣一名经验丰富的特工前去取回这条极 其重要的情报。双方约定的接头地点在  大厦的 1101房间。

 大厦 2103 1101 1102 1103 1104 1105 2103 3102 2101 2102 2103 2104 2105 3102 3101 3102 3103 3104 3105 K

同学录上的通讯地址:××大学×号楼 ××房间。 p q ch 2103 … 3102 'K' ch的地址 q的地址 字符数据 1101 2103 3102 地址也是一种数据: 街道上的门牌号码; 同学录上的通讯地址:××大学×号楼 ××房间。

地址和数据是否完全等价? 地址虽然是数据,但两者并非完全等价。 例如,假设用四个字节来描述一个地址, 那么从理论上说,其取值范围应该是: -231-231-1,但是在这些整数中,并不是 所有的整数都能成为一个有效的地址。

x p 8.1.2 地址与类型 ? 1000 访问一个变量需要 知道: 1000 (1)它的起始地址 (2)它的数据类型 对于内存: 8.1.2 地址与类型 1000 访问一个变量需要 知道: (1)它的起始地址 (2)它的数据类型 对于内存: 物理上,以字节为单元 逻辑上,以不同数据类 型为单元。 ? 1000 x 1020 p 1024 scanf(“%lf”, &x);

A pointer is a variable that contains the 8.1.3 什么是指针 The C Programming Language (K & R): A pointer is a variable that contains the memory address of another variable.

指针是一种变量,因而也具有变量的三个要素,但指 针是一种特殊的变量,其特殊性表现在它的类型和取 值上。具体而言: 变量名:与一般的变量命名规则相同; 变量的值:是另一个变量的内存地址; 变量的类型:包括两种类型, 指针变量本身的类型,即指针类型,它的长度为4个字节; 指针指向的变量的类型,指明了该变量的长度。 指针变量能否存放自己的地址?

指针 = 变量 ≠ 地址 清华大学 圆明园

8.2 指针变量 8.2.1 指针的定义 指针定义的一般形式: 基类型 *指针变量名; 例如: int x; /* 定义了一个整型变量 */ 8.2 指针变量 8.2.1 指针的定义 指针定义的一般形式: 基类型 *指针变量名; 例如: int x; /* 定义了一个整型变量 */ int *q; /* 定义了一个指向整型变量的指针变量 */

double * “pointer to double” char * “pointer to char” 现在我们有了三种新的数据类型: int * “pointer to int” double * “pointer to double” char * “pointer to char” 基类型 + 指针类型 = 新的数据类型(why?); 这三种数据类型的相同点:它们都是指针类型,它们的变量存放的都是地址,长度都是4个字节; 这三种数据类型的不同点:它们是不同的数据类型,相互之间不能替代使用。例如,当需要一个“pointer to int”类型的指针时,不能用一个“pointer to char”类型的指针来代替。

x y px 2 int x, y, *px; x = 2; 指向整型变量的指针 1000 1004 1008 一些问题: 如何把变量 x 的地址存放在指针 px 当中? 如何使用指针 px 来访问或修改变量 x 的值? 为何要如此自找麻烦? 1020 px 1024

x y px 8.2.2 指针运算符 1. &运算符 2 int x, y, *px; x = 2; 能否:px = 1000; 1000 8.2.2 指针运算符 1. &运算符 2 int x, y, *px; x = 2; 1000 x 1004 y 如何把变量 x 的地址存放在指针 px 当中? 1008 能否:px = 1000; 1000从何而来?编程时并不知道变量的实际地址; 类型不匹配,常量与指针, (int *)1000 1020 px 1024

x y px 2 int x, y, *px; x = 2; 我们想要做的事情: px = x 的内存地址; 我们写的语句: 1000 x 1004 y 1008 我们想要做的事情: px = x 的内存地址; 我们写的语句: 1020 px px = &x; 1024

x y px 2 1000 int x, y, *px; x = 2; px = &x; &:取地址运算符 1000 1004 1008 整型变量的指针类型。一般 的,如果x的类型是T,则 &x的类型为指向T型变量的 指针类型。 1020 px 1024

pointer_1 a &a pointer_2 b &b &a 即 a 的地址。

pointer_1 = &a; pointer_1 a 1000 1000 &a 1000

x y px 2. * 运算符 2 1000 int x, y, *px; x = 2; px = &x; 1000 1004 1008 2. * 运算符 2 1000 int x, y, *px; x = 2; px = &x; 1000 x 1004 y 1008 如何使用指针 px 来访问或修改变量 x 的值? 1020 px 1024

x y px 2 1000 int x, y, *px; x = 2; px = &x; 我们想要做的事情: y = px所指向的变量的值; 1004 y 1008 我们想要做的事情: y = px所指向的变量的值; 我们写的语句: 1020 px y = *px; 1024

x y px 2 1000 int x, y, *px; x = 2; px = &x; y = *px; 1004 y 1008 *:指针运算符(或称“间接访问”运算符) *px的类型是 int。一般的, 如果px的类型是指向T型变 量的指针类型,则 *px的类 型为T。 1020 px 1024

pointer_1 = &a; pointer_1 a 1000 1000 *pointer_1 &a 1000

a b c p q int a, b, c, *p, *q; a = 1; 1 b = 2; 2 13 c = 3; 3 1 p = &a; 0x0012FF7C 0x0012FF78 0x0012FF74 0x0012FF70 0x0012FF6C b = 2; 2 b 13 c = 3; c 3 1 p = &a; p 0012FF7C 0012FF78 q = &b; q 0012FF78 c = *p; p = q; *p = 13;

指针常量NULL表示一个指针不指向任何有效的数据。 int x, *p; p = &x; *p = 1; p = NULL; *p = 2; <stdio.h>

一个其值为NULL的指针不同于一个未初始 化的指针。一个指针在定义后,是未被初始 化的,其值是随机的,即可能指向某个无效 的地址,此时若对它进行访问,将会出错。 而NULL常量用来明确地说明一个指针不指 向任何有效的数据。

8.2.3 为何要使用指针? 指针的存在提供了一种共享数据的方法, 可以在程序的不同位置、使用不同的名字 8.2.3 为何要使用指针? 指针的存在提供了一种共享数据的方法, 可以在程序的不同位置、使用不同的名字 (指针)来访问相同的一段共享数据。如 果在一个地方对该数据进行了修改,那么 在其他的地方都能看到修改以后的结果。

输出结果: void MinMax(int x, int y, int min, int max) { min = x; max = y; if(x > y) max = x; min = y; } void main( ) int a, b, min, max; a = 4; b = 7; MinMax(a, b, min, max); printf("%d, %d\n", min,max); 输出结果: -858993460, -858993460

main的栈帧 a b min max 4 7 MinMax的栈帧 x y min max 4 7 4 7 void MinMax(int x, int y, int min, int max) { min = x; max = y; if(x > y) max = x; min = y; } void main( ) int a, b, min, max; a = 4; b = 7; MinMax(a,b,min,max); printf("%d, %d\n", min, max); main的栈帧 a b min max 4 7 MinMax的栈帧 x y min max 4 7 4 7

main的栈帧 a b min max 4 7 4 7 MinMax的栈帧 x y p1 p2 4 7 void MinMax(int x, int y, int *p1, int *p2) { *p1 = x; *p2 = y; if(x > y) *p1 = x; *p2 = y; } void main( ) int a, b, min, max; a = 4; b = 7; MinMax(a, b, &min, &max); printf("%d, %d\n", min, max); main的栈帧 a b min max 4 7 4 7 还有何方法能返回值? min、max的访问? MinMax的栈帧 x y p1 p2 4 7

问题描述: 计算一元二次方程的根。

void main( ) { double a, b, c, x1, x2; GetCoefficients(?a, ?b, ?c); /* 求解方程式的两个根x1, x2 */ SolveQuadratic(?a, ?b, ?c, ?x1, ?x2); /* 显示方程式的两个根x1, x2 */ DisplayRoots(?x1, ?x2); }

main( ) { double a, b, c, x1, x2; /* 从键盘读入方程式的系数a, b, c */ GetCoefficients(&a, &b, &c); /* 求解方程式的两个根x1, x2 */ SolveQuadratic(a, b, c, &x1, &x2); /* 显示方程式的两个根x1, x2 */ DisplayRoots(x1, x2); } 传值还是传地址?

void GetCoefficients(double *pa, double *pb, double *pc) { printf("请输入三个系数:\n"); printf("a: "); scanf("%lf", pa); printf("b: "); scanf("%lf", pb); printf("c: "); scanf("%lf", pc); }

void SolveQuadratic(double a, double b, double c, double *px1, double *px2) { double disc, sqrtDisc; if(a == 0) ... /* 出错处理 */ disc = b * b – 4 * a * c; if(disc < 0) ... /* 出错处理 */ sqrtDisc = sqrt(disc); *px1 = (-b + sqrtDisc) / (2 * a); *px2 = (-b - sqrtDisc) / (2 * a); }

指针分析1 *tinky = slinky; void main() /* 有何问题? */ { int binky; foo(&binky); } void foo(int *tinky) int slinky = 5; tinky = &slinky; *tinky = slinky;

指针分析2 thinking… void main() /* 输出结果是什么? */ { int* pinky; pinky = bar(); printf(“%d”, *pinky); } int* bar() int winky = 5; return(&winky); thinking…

指针分析3 void main() { int binky, *pinky; binky = 1; pinky = &binky; moo(*pinky, pinky); } void moo(int tinky, int *winky) int slinky = 2; *winky = tinky + slinky;

8.3 指针与数组 8.3.1 指向数组元素的指针 p a[0] int a[6], *p; p = &a[3]; a[1] a[2] 8.3 指针与数组 8.3.1 指向数组元素的指针 1000 a[0] int a[6], *p; p = &a[3]; 1004 a[1] 1008 a[2] 1012 a[3] p 1012 1016 a[4] 1020 a[5]

8.3.2 通过指针引用数组元素 main avg prices 5 FindAvg values nValues void main( ) 8.3.2 通过指针引用数组元素 void main( ) { double prices[5], avg; avg = FindAvg(prices, 5); } double FindAvg(double values[], int nValues) main avg prices FindAvg values nValues 5

a++; int a[6]; a[0] = 1; *a = 1; 在C语言中,一个数组的名字就代表了这个 数组的起始地址,它可以被视为一个其值 不可变的指针,该指针指向数组当中的第 一个数组元素。 int a[6]; a[0] = 1; *a = 1; 下标法: 指针法: 这两条语句是等效的 a++;

指针的算术运算 a int a[6]; a[k] 等效于 *(a +k); 指针的加减运算是以 数据元素为单元的。 a[0] a+1 1000 指针的加减运算是以 数据元素为单元的。 a[0] a+1 1004 a[1] a+2 int a[6]; a[k] 等效于 *(a +k); a 是指向地址1000的, 而 a + 2是指向1008的, 因为 a + k 的实际计算 方法是:a + k * sizeof(int) 1008 a[2] a+3 1012 a[3] a+4 1016 a[4] a+5 1020 a[5]

在指针的算术运算中,对基类型长度的补偿 是由系统自动来完成的。 double prices[10], x; int units[10], y; x = *(prices + 3); /* 把 x 赋值为prices[3] */ y = *(units + 3); /* 把 y 赋值为units[3] */ 在指针的算术运算中,对基类型长度的补偿 是由系统自动来完成的。

几种等效的表示方法 a[k] 等效于 *(a + k) &a[k] 等效于 a + k &a[0] 等效于 a

指针与数组的区别 int a[5]; pa int *pa; *pa = 2; a[0] a[1] a[2] a[3] a[4] 指针在使用前,必须先 初始化,指向一个有效 的地址。 int *pa; *pa = 2;

分析程序 int foobar( int *pi ) { *pi = 1024; return *pi; } int main() int ival = foobar( pi2 ); printf("%d", ival);;

合法的指针运算 指针加一个整数: ptr + k 指针减一个整数: ptr – k 两个指针相减: p1 – p2 指针的关系运算: >、>=、< <=、==、!=

问题描述: 计算一个数组所有元素之和。 方法一 int *ptr, values[10], sum, i; ... 输入各个数组元素的值 ... sum = 0; for(i = 0; i < 10; i ++) { sum += values[i]; }

方法二 int *ptr, values[10], sum, i; ... 输入各个数组元素的值 ... ptr = &values[0]; for(i = 0; i < 10; i ++) { sum += ptr[i]; }

方法三 int *ptr, values[10], sum, i; ... 输入各个数组元素的值 ... ptr = &values[0]; /* 也可写成 ptr = values */ sum = 0; for(i = 0; i < 10; i ++) { sum += *(ptr + i); }

方法四 ptr int *ptr, values[10], sum, i; ... 输入各个数组元素的值 ... for(i = 0; i < 10; i ++) { sum += *ptr; ptr ++; } ptr values

方法四 ptr int *ptr, values[10], sum, i; ... 输入各个数组元素的值 ... for(i = 0; i < 10; i ++) { sum += *ptr; ptr ++; } ptr values

方法四 ptr int *ptr, values[10], sum, i; ... 输入各个数组元素的值 ... for(i = 0; i < 10; i ++) { sum += *ptr; ptr ++; } ptr values

代码分析 void main() { int index, *aptr, *bptr; int arr[8]; aptr = &index; for (index=0; index<6; index++) arr[index] = index; bptr = aptr; aptr = &arr[2]; arr[*bptr] = *aptr; *aptr = *bptr; *bptr = *(aptr + 1); 画出此时内存状态(变量的取值) }

index aptr bptr 3 arr 1 6 3 4 5 2 0 1 2 3 4 5 6 7

int scores[MAX_SIZE]; 8.3.3 动态数组 在定义一个数组时,必须事先指定其长度。 例如: 或者: int a[6]; #define MAX_SIZE 100; int scores[MAX_SIZE];

有的时候,我们事先并不知道数组的确切 长度,只有当程序运行以后我们才知道。 因此,我们希望能够这样来定义数组: 但是在C语言中,这是不可能的。 int n; printf(“请输入学生人数:”); scanf(“%d”, &n); int scores[n];

int *scores; 我们能做的事情是:动态地为该数组分配 所需的内存空间,即在程序运行时分配。 具体做法是:定义一个指针,然后把动态 分配的内存空间的起始地址保存在该指针 中,如: int *scores; scores = 动态分配的内存空间的起始地址;

void *malloc(size_t size) 动态数组 void *malloc(size_t size) 参数:申请的字节数; 功能:申请一块内存; 返回值:一个指向该内存的指针或NULL; 头文件:stdlib.h int *scores, n; printf(“请输入学生人数:”); scanf(“%d”, &n); scores = (int *)malloc(n*sizeof(int));

比较静态数组与动态数组 #define NUM_SCORE 10 int scores[NUM_SCORE]; for(i = 0; i < NUM_SCORE; i++) scores[i] = 0; 区别: 1. 长度固定/可变 2. 空间分配时机 3. 空间的回收 int *scores, nScores; scanf(“%d”, &nScores); scores = (int *)malloc(nScores * sizeof(int)); for(i = 0; i < nScores; i++) scores[i] = 0;

int *scores, nScores; scanf(“%d”, &nScores); scores = (int *)malloc(nScores * sizeof(int)); if(scores == NULL) ... /* 内存分配失败 */ scores[3] = 100; 等价于:*(scores + 3) = 100;

作为局部变量的数组在函数调用结束后, 其内存空间即被释放。而对于动态数组, 即使在函数调用结束后,其内存空间依然 存在。

程序 分析 4 4344416 1244916 4198895 1 #define MAX_SIZE 10 void main( ) { int a[5] = {1, -1, 2, -2, 0}; int b[5] = {3, 1, -2, 4, 1}; int *c, i; c = Add(a, b, 5); for(i = 0; i < 5; i++) printf("%d ", c[i]); } int *Add(int a[], int b[], int num) int i, c[MAX_SIZE]; for (i = 0; i < num; i++) c[i] = a[i] + b[i]; return c; 4 4344416 1244916 4198895 1

4 0 0 2 1 void main( ) { int a[5] = {1, -1, 2, -2, 0}; int b[5] = {3, 1, -2, 4, 1}; int *c, i; c = Add(a, b, 5); if(c == NULL) return; for(i=0; i<5; i++) printf("%d", c[i]); } int *Add(int a[], int b[], int num) int i, *c; c =(int*)malloc(MAX_SIZE*sizeof(int)); if(c == NULL) return(NULL); for (i = 0; i < num; i++) c[i] = a[i] + b[i]; return c; 4 0 0 2 1

操作系统 程序 栈帧2 栈帧1 全局变量 内存分布状况 堆 动态分配 栈 自动分配 全局变量区域 静态分配

内存分布状况 操作系统 程序 全局变量 int *price, *unit; price = malloc(n*sizeof(int)); 块1 块2 全局变量 int *price, *unit; price = malloc(n*sizeof(int)); unit = malloc(n*sizeof(int)); 内存分布状况 堆 栈 price unit 全局变量区域 vc…

动态分配 values = malloc(5 * sizeof(int)); scores = malloc(3 * sizeof(double)); 堆由堆管理器管理,用户具有排他性使用权 栈 堆 values scores

动态分配 values = malloc(5 * sizeof(int)); scores = malloc(3 * sizeof(double)); values = NULL; values原来指向的内存单元现在无法访问。 栈 堆 内存泄漏! values 空 scores

动态分配 values = malloc(5 * sizeof(int)); scores = malloc(3 * sizeof(double)); free(values); values[0] = 1; values = NULL; 空 栈 堆 values scores

代码分析 void main() { int i, *a, *b, list[4]; a = &i; for (i = 0; i < 4; i++) list[i] = *a; b = (int*)malloc(4*sizeof(int)); a = list + 2; *b = *a; b++; 画出此时内存状态(栈、堆、变量的取值) }

栈 main 4 i a b 堆 2 ? 1 2 3 list

8.3.4 指针? 数组? 在C语言中,一个指针可能是指向单一的一 个变量,也可能是指向一组相同类型的数 8.3.4 指针? 数组? 在C语言中,一个指针可能是指向单一的一 个变量,也可能是指向一组相同类型的数 组元素,从外观上,两种指针的形式完全 一样,因此只能靠程序员去加以区分。 int x, a[6], *ptr; ptr = &x; /* 合法 */ ptr = a; /* 合法 */ ptr x? a?

分析结果 1 15 5 2 9 15 void main( ) { int a[ ] = {1,3,5}; int b[ ] = {2,4,6}; int *p; p = &a[0]; bar(p); printf("%d %d %d\n",a[0],a[1],a[2]); p = &b[0]; p ++; foo(p); printf("%d %d %d\n",b[0],b[1],b[2]); } void bar(int p2[ ]) { p2[1] = 15; } void foo(int p1[ ]) *p1 += 5; 1 15 5 2 9 15

8.3.5 二维数组与指针 1. 指针数组 一个数组,其元素均为指针类型变量,称为 指针数组。即数组中的每一个元素都是一个 指针。 8.3.5 二维数组与指针 1. 指针数组 一个数组,其元素均为指针类型变量,称为 指针数组。即数组中的每一个元素都是一个 指针。 定义形式:类型名 * 数组名[数组长度]; 例如: int *pa[4];

指针数组的使用 int *pa[4]; char *pb[4]; pa[0] pa[1] pa[2] pa[3] pa[4] pb[0] x pa[0] pa[1] pa[2] pa[3] pa[4] pb[0] pb[1] pb[2] pb[3] pb[4] Hello! y Basic z Computer m Software n Tsinghua

main(int argc, char *argv[ ]); 例如,假设程序名为sort,在运行时命令行 的情况如下: sort source.txt destination.txt argv[0] argv[1] argv[2] argc = 3 sort命令

2. 指向指针的指针 指向指针的指针:一种特殊的指针,其基 类型也为指针类型。如 int **p。 通常的指针: 指向指针的指针: p 2. 指向指针的指针 指向指针的指针:一种特殊的指针,其基 类型也为指针类型。如 int **p。 通常的指针: 指向指针的指针: p ptr ptr *p x *ptr x *ptr **p

已有的三种指针类型: int * “pointer to int” double * “pointer to double” char * “pointer to char” 现在又可以增加三种新的数据类型: int ** “pointer to pointer to int” double ** “pointer to pointer to double” char ** “pointer to pointer to char”

在foo函数中修改主函数中的变量 void main() { int value; foo(???); } void foo(???) //p ??? = 1; value p 1

如果主函数中的变量是一个指针呢? void main() { int *a; foo(???); } a void foo(???) //p ??? = malloc(8); a p

main() { int count, *randoms; count = GetRandomArray(randoms); printf(“最后的那个随机数是:%d\n”, randoms[count-1]); } int GetRandomArray(int array[]) { int i, count; printf(“需要多少随机数? ”); scanf(“%d”, &count); array = (int *) malloc(count * sizeof(int)); srand((unsigned)time(NULL)); for (i = 0; i < count; i++) array[i] = rand( ); return count; } 程序 找错

main的栈帧 … array 动态数组 (*array) main() { int count, *randoms; count = GetRandomArray(&randoms); printf(“最后的那个随机数是:%d\n”, randoms[count-1]); } int GetRandomArray(int **array) { int i, count; printf(“需要多少随机数? ”); scanf(“%d”, &count); *array = (int *) malloc(count * sizeof(int)); srand((unsigned)time(NULL)); for (i = 0; i < count; i++) (*array)[i] = rand( ); return count; } 动态数组 main的栈帧 count randoms (*array) GetRandomArray … array

函数之间的地址传递方法: 如果你是想把主函数中的地址,从上往下传,传到被调用的函数里面,那么形参和实参均为指针; 如果你是想把被调用函数中的地址,从下往上传,传到主函数当中去,那么形参和实参均为指向指针的指针。

1 … ① 0012ff7c ③00430430 ⑥ 0012ff7c ② 0012ff7c ⑤ 0012ff70 ⑦ 0012ff74 void main( ) { int homer, *lisa; int *bart[3]; lisa = &homer; bart[0] = lisa; *(bart + 2) = malloc(2*sizeof(int)); Test(lisa, &bart[1]); } void Test(int *marge, int **maggie) { *maggie++ = marge; **maggie = 1; } 430430 homer (int ) 12ff7c lisa (int *) ① 0012ff7c 12ff78 bart[2] (int *) ③00430430 12ff74 bart[1] (int *) ⑥ 0012ff7c 12ff70 bart[0] (int *) ② 0012ff7c 12ff6c 12ff18 marge (int *) 12ff1c maggie (int **) ⑦ 0012ff74 ⑤ 0012ff70 ④ 0012ff7c

3. 二维数组与指针 int a[3][4] = {{1, 3, 5, 7}, {9, 11, 13, 15}, {17, 19, 21, 23}}; a 是一个数组名,它包含有3行、4列, 共12个数组元素。a[0]、a[1]和a[2]都可 看成是一个一维数组名,而a[i][j]就表示 数组第 i 行第 j 列的那个数组元素。

a 在定义二维数组 int a[3][4]之后,内存的分布 情况如下: 0x0012FF7C 0x0012FF78 内存分布: 按行存放, 先顺序存放 第一行的所 有元素,再 存放第二行 的元素。 0x0012FF74 0x0012FF70 a[2] 0x0012FF6C 0x0012FF68 0x0012FF64 0x0012FF60 a[1] 0x0012FF5C 0x0012FF58 0x0012FF54 a[0] 0x0012FF50 a

怎么做?如何定位a[i][j]? 程序员眼中的二维数组:一个3×4的二 维表格; 内存分布:连续存放的数据元素; C编译器的任务:把程序员眼中的二维表 格映射到内存当中的相应元素。 怎么做?如何定位a[i][j]?

a[0]、a[1]、a[2]的处理:它们都是一个一维数组名,类似于一个指针,可以进行指针的各项操作,但并没有占用实际的内存空间; a[i][j]的定位: a[0]、a[1]、a[2]的处理:它们都是一个一维数组名,类似于一个指针,可以进行指针的各项操作,但并没有占用实际的内存空间; 数组名a的处理:类似于一个特殊的指针,它没有占用实际的内存空间,而且它指向的是一个固定长度的一维数组,故每一步的跨度为该数组的长度。 对应于内存首地址开始的第 k个 数组元素,k = i*每行的元素个数 + j,它的内 存地址为a + k * sizeof(int);

a 1 3 5 7 9 11 13 15 17 19 21 23 a+1 a+2 a[0] a[0]+1 a[0]+2 a[0]+3 2000 2004 2008 2012 a+1 2016 2020 2024 2028 a+2 2032 2036 2040 2044

int test1(???); int test2(???); void main( ) { int a[3][4], i; for(i = 0; i < 3; i ++) { test1(a[i]); /* 处理第i行的数组元素 */ } test2(a); /* 处理整个数组 */ } int test1(???); int test2(???);

test1函数:a[i]是一个一维数组名,因此可以把形参定义为int *pa(或int pa[ ]、 int pa[4]); test2函数:a是一个二维数组名,它指向的是长度为4个数组元素的一维数组。因此我们需要引入一种新的指针类型:即指向由若干个元素所组成的一维数组的指针,如:int (*arr)[4]。当然,也可使用传统方法,即把形参设定为int arr[3][4],或 int arr[ ][4],它们都是等价的,都是定义了一个指针,4个字节。

int *p; char *p; int **p; int (*p)[2]; char (*p)[3]; p = malloc(100); p++; p

int test1(int *pa) { … pa[1] = 2; } int test2(int (*arr)[4]) arr[i][j] = 2;

4. 动态的二维数组 在C语言中,用malloc函数动态分配的是一 整块的连续的内存空间,并返回其起始地 4. 动态的二维数组 在C语言中,用malloc函数动态分配的是一 整块的连续的内存空间,并返回其起始地 址,但这并不能作为一个二维数组来使用, 因为编译器缺乏足够的信息来进行地址的 运算。例如,不能采用以下方法来动态地 定义一个二维数组a[3][4]: int *a; a = (int *)malloc(12 * sizeof(int)); 因为编译器无法计算出元素a[i][j]的地址。

方法一 对于静态二维数组a[3][4],可以把a看成是 一个特殊的一维数组,它的三个元素a[0]、 因此可以采用指针数组的方法。 a (int **) 指针数组

int **CreateGrid(int nRows, int nCols) { int **array, i; array = (int **)malloc(nRows * sizeof(int *)); for(i = 0; i < nRows; i ++) array[i] = (int *)malloc(nCols * sizeof(int)); return(array); } main( ) { int **a, i, j; a = CreateGrid(3, 4); a[i][j] = ... FreeGrid(a, 3, 4);

方法1与静态二维数组的区别: 存储位置不同:静态二维数组是位于该函数的栈帧当中,当函数调用结束后,即被释放;而方法1创建的动态数组位于堆空间,必须用free函数来显示地释放; 占用空间不同:静态二维数组的a和a[0]、a[1]和a[2]都不占用内存空间,而方法1的a是指向指针的指针,a[0]、a[1]和a[2]都是指针,它们都需要占用4个字节的内存空间; 对a[i][j]的定位方式不同:静态二维数组方式:a + i*每行元素个数 + j;方法1:间接访问的方式(非连续存放):*(a + i) + j。

方法二 基本思路:把指针a设定为指向由若干个元 素所组成的一维数组的指针,而不是指向 指针的指针,如:int (*a)[4]。 a+1

int *CreateGrid(int nRows, int nCols) { int *array; array = malloc(nRows * nCols * sizeof(int)); return(array); } main( ) { int (*a)[4], i, j; a = (int (*)[4]) CreateGrid(3, 4); a[i][j] = ...

8.4 指针与字符串 8.4.1 字符串的表示形式 1. 字符串的存储 "China" 字符串存储在字符数组当中,并以字符‘\0’ 8.4 指针与字符串 8.4.1 字符串的表示形式 1. 字符串的存储 字符串存储在字符数组当中,并以字符‘\0’ 来作为结束标记。 "China" C h i n a \0

  2. 字符数组方式 基本思路:首先定义一个字符数组,然后用它来存放一个字符串; 2. 字符数组方式 基本思路:首先定义一个字符数组,然后用它来存放一个字符串; 每个数组元素只能存放一个字符,因此字符数组的长度必须大于所存放的字符串的长度; 对字符数组只能对各个元素赋值,不能用以下方法对整个字符数组赋值: char str[14]; str = "hello, world"; 可以在定义字符数组的时候对它进行初始化: char str[ ] = "China"; 它等价于char str[ ] = {'C', 'h', 'i', 'n', 'a', '\0'};  

3. 字符串常量 字符串常量:用一对双引号括起来的一串字符序列,如"hello, world" 、"China"; 3. 字符串常量 字符串常量:用一对双引号括起来的一串字符序列,如"hello, world" 、"China"; 字符串常量的使用方式:单独使用或用一个指针来指向它,例如: printf("The answer is %.2f. \n", x); strcpy(str, "hello, world"); char *str; str = "hello, world"; 如果用指针来指向一个字符串常量,那么该指针当中存放的是字符串的首地址,而不是把字符串整个放在指针中。

字符串常量存放在什么地方? 操作系统 程序 全局变量 char *s1, *s2; s1 = "hello, world"; "China" 堆 栈 全局变量 char *s1, *s2; s1 = "hello, world"; s2 = "China"; 数据段 s1[0] = 'c'; // ?? 存放在“数据段” (Data Segment) 中,由系统自动来 管理。 s1 s2

8.4.2 字符串的访问 对字符串中某个字符的访问方法: 数组下标法:如 str[i]; 指针法:如 *(p + i),*p; 8.4.2 字符串的访问 对字符串中某个字符的访问方法: 数组下标法:如 str[i]; 指针法:如 *(p + i),*p; 对字符串的访问方法: 数组名法:如 printf(“%s”, str); 指针法:如 p = str;printf(“%s”, p);

调整字符顺序 问题描述: 编写一个程序,输入一个字符串,该字符串只包含三种类型的字符:小写字母、大写字母和空格。然后生成一个新的字符串,把所有的小写字母放在最前面,所有的空格放在中间,所有的大写字母放在最后,而且这些小写、大写字母原来的顺序不能乱。最后把这个新的字符串输出。 讨论 输入字符串:B_a_Ab ab__BA

1. 计算出各部分的长度; 2. 用三个指针分别指向其起始位置 3. 逐个字符的拷贝 pLow pBlank pUp

void main() { char src[100], dest[100]; int i, length; int up, low, blank; char *pUp, *pLow, *pBlank; up = 0; low = 0; blank = 0; gets(src); length = strlen(src); for(i = 0; i < length; i++) if(src[i] >= 'a' && src[i] <= 'z') low++; else if(src[i] >= 'A' && src[i] <= 'Z') up++; else if(src[i] == ' ') blank++; }

pLow = dest; pBlank = pLow + low; pUp = pBlank + blank; for(i = 0; i < length; i++) { if(src[i] >= 'a' && src[i] <= 'z') *pLow++ = src[i]; else if(src[i] >= 'A' && src[i] <= 'Z') *pUp++ = src[i]; else if(src[i] == ' ') *pBlank++ = src[i]; } dest[length] = '\0'; printf("%s\n", dest);

8.4.3 指针字符串练习题 1. 下列语句中,不能把字符串“Hello!”赋给 字符数组b的语句是( ) 8.4.3 指针字符串练习题 1. 下列语句中,不能把字符串“Hello!”赋给 字符数组b的语句是( ) char b[10] = {'H', 'e', 'l', 'l', 'o', '!'}; char b[10]; strcpy(b, "Hello!"); char b[10]; b = "Hello!"; char b[10] = "Hello!"; C

2. 对于变量的声明 int (*pa)[4],下列描述中正确的是( ) pa是一个指向某数组中第四个元素的指针,该元素是int型变量; pa是一个具有4个元素的指针数组,每个元素是一个int型指针; 假设pa = 1000,那么执行语句pa++后,pa的值为1004; pa是一个指向数组的指针,所指向的数组是4个int型元素。 D

3. 程序找错 char *GetMorning( ) { char str[] = “Morning”; return str; } 3. 程序找错 char *GetMorning( ) { char str[] = “Morning”; return str; } void main( ) char msg[32]; int i; for(i = 1; i <= 32; i++) msg[i] = ‘\0’; /* 将msg 清零 */ msg = “Good”; /* 拷贝“Good” 字符串 */ msg[5] = ‘ ’; /* 添加一个空格 */ strcat(msg, GetMorning()); /* 添加”Morning”字符串 */ printf(“%s”, msg);

4. 程序分析 void func2(char *str) { int len, i, j; char val[50], *p; len = strlen(str); i = 0; j = len − 1; p = val; while((i < len / 2) && (j >= len / 2)) { *p++ = str[j--]; *p++ = str[i++]; } *p = 0; strcpy(str, val); } void main( ) { char str[50] = "ogauain!sotltrnc"; func2(str); printf("%s", str); } 输出结果是: ____________________ congratulations!

- END -