Via http://www.matrix67.com/blog/article.asp?id=271 by Matrix67
目录:
C语言速成手册(一):基本数据类型、标准输出、函数
C语言速成手册(二):布尔值、条件判断、循环
C语言速成手册(三):数组、字符串、结构
C语言速成手册(四):指针、动态内存分配、标准输入
C语言速成手册(五):其它运算符、文件操作、其它函数
C语言速成手册(六):其它问题、后记
指针的定义
定义一个指针的方法如下:
1 |
类型名 *指针名; |
例如,下面的语句定义了一个指针:
1 |
int *pointer; |
这样,pointer就是一个指针,它指向的是一个int类型的数据。
一个指针可以是一个合法的内存地址,也可以为0(通常写成NULL)。
你可以用printf语句输出一个指针,对应的标识为"%p"。下面的代码可以输出上面定义的指针指向的地址。
1 |
printf("%p",p); |
取地址与引用
假如a是一个变量,p是一个指针,那么&a返回该变量的地址,*p返回该指针所指的内容(称做“引用”)。
阅读下面的代码片段:
1 2 3 4 5 6 7 |
int *p; int a = 520; p = &a; printf( "%p -> %d\n", p, *p ); *p = 1314; printf( "%p -> %d\n", p, *p ); printf( "a = %d", a ); |
程序输出如下。当执行了p=&a后,存取*p就相当于是存取变量a了。
1 2 3 |
0022FF78 -> 520 0022FF78 -> 1314 a = 1314 |
动态内存分配
首先介绍sizeof函数(准确地说是一个运算符),它的参数为一个变量名或类型名,返回的是它所占内存空间的大小。下面的代码输出1 8 800 4 1 。
1 2 3 4 5 6 7 8 |
long long a; double b[100]; _Bool *c; printf( "%d " , sizeof(char) ); printf( "%d " , sizeof(a) ); printf( "%d " , sizeof(b) ); printf( "%d " , sizeof(c) ); printf( "%d " , sizeof(*c) ); |
下面介绍四种动态内存分配函数,使用它们前需要在程序最前面包含头文件stdlib.h。四种函数的格式分别为:
1 2 3 4 |
void *malloc ( size ); void *calloc ( n, size ); void free ( pointer ); void *realloc( pointer, size ); |
函数malloc将在内存里寻找一个大小为size的连续空间,把分配到的内存地址作为一个指向void类型的指针(默认的无类型指针)返回。如果空间分配失败,函数返回NULL。
函数calloc将在内存里寻找一个大小为n * size的连续空间,并且把这段内存的数据全部清0,返回数据和malloc一样。如果空间分配失败,函数返回NULL。
函数free用于释放内存空间,释放后的空间被回收,可以用于以后的malloc或calloc操作。
函数realloc在保证已有数据不变的情况下改变已有指针的空间大小,返回重新分得的空间的内存地址(有可能和原来不同)。如果空间重新分配失败,函数返回NULL。
Pascal中的new语句可以用前两个函数代替,free语句则相当于Pascal中的dispose。
注意,malloc和calloc函数所返回的指针还没确定类型,理论上需要用类型转换。下面的程序合法地为p指针分配空间:
1 2 3 |
int *p; p = (int *) malloc( sizeof(int) ); *p = 520; |
事实上,由于赋值时C语言自动转换类型,因此那个类型转换是没有必要的(去掉(int *)没有影响)。
指针与结构
一个指针可以指向一个结构,一个结构也可以包含一个指针。结构里包含一个指向结构的指针就构成了链表:
1 2 3 4 |
struct node{ int value; struct node *next; } |
这样,定义struct node *a,则(*a).next就是另一个指向node结构的指针。在C语言中,(*x).y的句型很常用,因此有一个专门的记号x->y来代替(*x).y这样繁杂的写法。
你可以从下面的程序中看到链表的使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#include <stdio.h> #include <stdlib.h> struct node { int value; struct node *next; }; int main() { struct node *head = NULL; int i; for(i=1;i<=10;i=i+1) { struct node *newNode; newNode = malloc( sizeof(struct node) ); newNode->value = i; newNode->next = head; head = newNode; } struct node *p = head; while (p) { printf( "%d\n", p->value ); p = p->next; } return 0; } |
指针与函数
前面说过,C语言中的函数参数和变量只能够供该函数使用。
下面四个程序代码的输出分别是什么?
代码一:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <stdio.h> void swap( int a, int b ) { int c = a; a = b; b = c; } int main() { int a = 520, b = 1314; swap( a , b ); printf( "%d %d", a, b ); return 0; } |
代码二:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <stdio.h> int a = 520, b = 1314; void swap( int a, int b ) { int c = a; a = b; b = c; } int main() { swap( a , b ); printf( "%d %d", a, b ); return 0; } |
代码三:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <stdio.h> int a = 520, b = 1314; void swap() { int c = a; a = b; b = c; } int main() { swap(); printf( "%d %d", a, b ); return 0; } |
代码四:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <stdio.h> void swap( int *a, int *b ) { int c = *a; *a = *b; *b = c; } int main() { int a = 520, b = 1314; swap( &a, &b); printf( "%d %d", a, b ); return 0; } |
答案:前两个程序输出520 1314,后两个程序输出1314 520。
前两个程序中,待交换的两个数(即使是全局变量)作为参数传给了swap函数,该函数里的操作对函数外无影响。
第三个程序中,swap函数对全局变量直接进行操作,其影响是全局的。
最后一个程序巧妙地应用了指针来实现两数交换。函数的参数是指针类型,这个函数不能改变指针本身,但可以改变指针所指的内容。这是写此类函数通常所用的方法。
为了强调函数void swap( int *a, int *b )中的指针本身不发生改变,很多地方喜欢写成void swap(const int *a, const int *b ) 。
指针与数组
数组由内存的连续空间构成,因此可以用指针来访问。事实上,数组名本身就是一个指针。观察下列代码:
1 2 3 4 5 6 7 |
int a[100]; printf("%p\n", &a[0]); printf("%p\n", a); printf("%d\n", a == &a[0] ); printf("%p\n", &a[1]); printf("%p\n", a+1); |
看看输出结果(自己运行),你会发现,数组名其实就是一个指向数组起始位置的指针,其作用相当于&a[0]。而指针本身可以进行加减运算(表示内存地址的加减,具体加多少减多少取决于指针所指的类型),其本质是在数组中进行定位。因此,下面的两个代码是完全等价的:
1 2 3 4 |
int i, a[100]; for ( i=0; i<100; i=i+1 ) *(a+i)=i; int i, a[100]; for ( i=0; i<100; i=i+1 ) a[i]=i; |
下面的程序输出0 1 2 3 4 0。函数init不能改变变量的值,但由于数组的实质是指针,因此它可以改变数组所储存的内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <stdio.h> void init( int a[5], int b ) { int i; for ( i=0; i<5; i=i+1 ) a[i]=i; b = 5; } int main() { int i, a[5], b=0; init( a, b ); for ( i=0; i<5; i=i+1 ) printf("%d ",a[i]); printf( "%d\n", b ); return 0; } |
你甚至可以用指针来创建数组。使用函数calloc可以方便地得到指定长度并已全部初始化为0的数组。再加上realloc函数后,你就可以实现真正意义上的动态数组(长度可变)。示例程序如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#include <stdio.h> #include <stdlib.h> int main() { int i, *a; a = calloc(sizeof(int),5); for ( i=0; i<5; i=i+1 ) *(a+i)=i; for ( i=0; i<5; i=i+1 ) printf("%d",a[i]); a = realloc(a,sizeof(int)*10); for ( i=5; i<10; i=i+1 ) a[i]=10-i; for ( i=0; i<10; i=i+1 ) printf("%d",a[i]); return 0; } |
上面的程序将输出012340123454321。
标准输入
之所以现在才来说读入操作,是因为读入函数需要用到指针知识,否则解释不清楚。
输入操作用scanf函数,和输出一样需要有stdio.h支持。
和输出操作一样,scanf函数的第一个参数也是一个字符串,读入同样采用匹配标识符的方法进行。所不同的是,读入函数的参数是需要随函数变化的,因此变量作为scanf的参数时需要加上取地址符号,作为指针代入函数才行。除了读入%c类型之外,所有读入操作均自动跳过空格。
看下面四个例子:
代码一:
1 2 |
int a, b; scanf( "%d%d" , &a , &b ); |
输入:
8 2
则a为8,b为2。
===========性感的分割线===========
代码二:
1 2 3 |
int a; char b; scanf( "%d%c" , &a , &b ); |
输入:
8 2
则a为8,b为一个空格。
===========性感的分割线===========
代码三:
1 2 |
int h, m, s; scanf( "%d:%d:%d" , &h , &m, &s ); |
输入:
20:19:02
则h为20,m为19,s为2。
输入:
20:19-02
则h为20,m为19,s没有获得新的值(匹配失败)。
===========性感的分割线===========
代码四:
1 2 |
char st[80]; scanf( "My favourite website is%s" , st ); |
输入:
My favourite website is matrix67.com, I love it!
则st为"matrix67.com," 。
注意最后这个例子,st是数组,本质上是一个指针,因此不需要加&符号。