晏文靖 yanwenjing@wxic.edu.cn 第八单元 应用指针程序设计 从现在开始,将详细讲述C语言的方方面面。第一章中的所有疑惑,都将一一消灭。 本章将讲述类型、变量、常量、数组等。这些概念的建立是进行进一步C语言学习的必要条件。同时,这些概念也是各种高级语言的共通概念。 晏文靖 yanwenjing@wxic.edu.cn
内存(Random Access Memory) 地址(Address) 所有活动指令和数据都保存在内存内 速度快,但是掉电即失(挥发性) 可以随机访问 只要指明要访问的内存单元的地址,就可以立即访问到该单元 地址是一个无符号整数(习惯用16进制数表示),其字长与机器相同 内存中的每个字节都有唯一的一个地址
指针的故事 “该程序执行了非法操作,即将关闭” 黑客攻击服务器利用的bug绝大部分都是指针和数组造成的 某些用C的人,尽量避免使用指针 这种错误几乎全是由指针和数组导致的 黑客攻击服务器利用的bug绝大部分都是指针和数组造成的 某些用C的人,尽量避免使用指针
指针的故事 是“稀饭”最挚爱的武器 很多“Mission Impossible”由指针完成 稀饭 == C Fans 大多数语言都有无数的“不可能” 而C语言是 “一切皆有可能” —— “Impossible is Nothing” ——
指针(Pointer) int *p; 定义了一个指针变量p,简称指针p p用来保存地址 此时这个地址是 哪呢 (p指向哪呢)? p是变量,int*是类型 变量都占用内存空间,p的大小是sizeof(int*) p用来保存地址 此时这个地址是 哪呢 (p指向哪呢)?
指针(Pointer) int i; p = &i; *p就是普通变量 p可以动态(任意)地指向不同内存,从而使*p代表不同的变量 p = &a[0];
指针 指针也是数据类型。指向不同数据类型的指针,分别为不同的数据类型 指针指向非其定义时声明的数据类型,将引起warning int*、float*、char*、int**、int***…… 指针指向非其定义时声明的数据类型,将引起warning void*类型的指针可以指向任意类型的变量 指针在初始化时一般int *p=NULL; NULL表示空指针,即无效指针 但它只是逻辑上无效,并不是真正地无效
&与*运算符 &运算的结果是指向该变量的指针 思考: *和指针的组合是一个变量,该变量的地址和类型分别是指针指向的地址和指针定义时指向的类型 int i, *p; p = &i; int *p, a[10]; p = a; int *p, a[10]; p = &a[0]; int *p, a[10]; p = &a[5]; 思考: int *p, a[10]; p = &a; *和指针的组合是一个变量,该变量的地址和类型分别是指针指向的地址和指针定义时指向的类型 int i, *p; p = &i; *p = 0; int *p, a[10]; p = a; *p = 0; int *p, a[10]; p = &a[1]; *p = 0; int *p; float f; p = (int*)&f; *p = 1;
指针与数组 数组名“可以”看作一个指针 只是不能修改这个指针的指向 常指针 int a[10]; 值为数组首地址
指针与数组 指针可当作数组名使用,反之亦然 int *p, a[10]; p = a; p[1] = 0; *a = 0;
指针运算 int* p = NULL; p++; /* p的值会是多少? */ 指针的加减运算是以其指向的类型的字节宽度为单位的 int *p, a[10]; p = a; *(p+3) 等价于 a[3] p++; *p 等价于 a[1]
指针运算 int *p, *q, a[10]; p = a; q = &a[2]; 运算法则 q - p == ? q = p + 3; 只能进行加减和关系运算 只能同类型指针之间或指针与整数之间运算 “q=p+3;”是合法语句,由此可以知道“q=p;”也是合法的。我们一直强调指针就是一个变量,所以变量之间的赋值操作自然在指针上也有效。
指针与函数 Not Work 指针既然是数据类型,自然可以做函数的参数和返回值的类型 指针做参数的经典例子: main() { int x, y; swap(x, y); } void swap(int x, int y) { int temp; temp = x; x = y; y = temp; } Not Work
这里的函数调用过程还是“实参” 的内容复制到“形参” ,千万不要理解成什么 “传引用调用” 指针做参数 这里的函数调用过程还是“实参” 的内容复制到“形参” ,千万不要理解成什么 “传引用调用” main() { int x, y; swap(&x, &y); } void swap(int *px, int *py) { int temp; temp = *px; *px = *py; *py = temp; }
2个月使用scanf目睹之怪现状 int i; scanf("%d", i); /* 这样会如何?*/ i的值被当作地址。如i==100,那么输入的整数就会从地址100开始写入内存 int i; scanf("%d", i); /* 这样会如何?*/ int i; scanf("%f", &i); /* 这样又会如何?*/ char c; scanf("%d", &c); /* 这样呢?*/ 输入被当作float,以float的二进制形式写到i所在的内存空间 输入以int的二进制形式写到c所在的内存空间。c所占内存不足以放下一个int,其后的空间也被覆盖
数组做参数 其实就是指针做参数 这里给定元素个数有意义吗? int a[10]; ProcessArray(a); 不是把整个数组的内容复制到函数内 int a[10]; ProcessArray(a); void ProcessArray(int* p) { ...... } void ProcessArray(int a[]) { ...... } 这里给定元素个数有意义吗?
数组做参数 我们应该让函数知道数组的大小 int a[10]; ProcessArray(a, 10); void ProcessArray(int a[], int n) { ...... }
数组做参数示例 main() { int a[10]; RandFill(a, 10); } void RandFill(int a[], int size) { int i; for (i=0; i<size; i++) a[i] = rand(); }
二维数组做参数 int a[10][20]; ProcessArray(a, 10); void ProcessArray(int a[][20], int m) { ...... } 这个必须 给出!
数组做返回值? ???? ProcessArray(int a[], int n) { ...... return a; } 上面语句返回的是什么类型? int* const 函数没有返回数组内容的可能,只能返回指针
指针和数组做参数 通过参数,把数据传回给调用者 通过一个参数把大量的数据送到函数内 如果只向内传送数据,就把参数定义为const,防止意外修改数据,也让函数的功能更明确 void PrintArray(const int * p, int n) { ...... } void PrintArray(const int a[], int n) { ...... }
字符串 (String) 与字符数组、字符指针 一串以'\0'结尾的字符在C语言中被看作字符串 用双引号括起的一串字符是字符串常量,C语言自动为其添加'\0'终结符 "Hello world!" 把字符串常数作为表达式直接使用,值是该常数的地址,类型为const char* C语言没有字符串类型,完全用字符数组和字符指针处理 字符数组 每个元素都是字符类型的数组 char string[100]; 字符指针 指向字符类型的指针 char* p;
字符串“赋值”还是“复制”? char* string; string = "Free your mind"; 指针赋值 char string[] = "Free your mind"; 内容复制 char string[20]; string = "Free your mind"; 非法! char string[20]; strcpy(string, "Free your mind"); String copy!
字符串处理函数 在<string.h>中定义了若干专门的字符串处理函数 strcpy: string copy char *strcpy(char *dest, const char *src); strlen: string length size_t strlen(const char *s); strcat: string catenate char *strcat(char *dest, const char *src); strcmp: string comparison int strcmp(const char *s1, const char *s2); strstr: locate a substring char *strstr(const char *s1, const char *s2);
’\0’作字符串终结符的先天不足 假若字符串没有'\0',那么这些字符串处理函数将怎样? 会一直进行处理,直到遇到一个'\0'为止。此时可能已经把内存弄得乱七八糟 ANSI C定义了一些“n族”字符串处理函数,包括strncpy、strncat、strncmp等,通过增加一个参数来限制处理的最大长度 确认:要写入字符串的地方存储空间是否足够放下所有字符和'\0'
指针、数组以及其它的类型组合 基本数据类型和修饰符 指针是一种数据类型 数组也是一种数据类型 任何类型都可以做指针或者数组的基础类型 int、char、float、double long、short、const、signed、unsigned 指针是一种数据类型 是从其它类型派生的类型 指向XX类型的指针 数组也是一种数据类型 有n个XX类型的元素 任何类型都可以做指针或者数组的基础类型 它们自己也可以做彼此或自己的基础类型 类型套类型。一个类型派生出新的类型,新的类型又能派生出新的类型,再派生出新的类型,再再派生出新的类型……子子孙孙,无穷尽也