C语言摘要 -- K&R笔记(3)

来源:互联网 发布:精子优化处理检查 必要 编辑:程序博客网 时间:2024/06/05 09:45

本文是对《K&R》第五章《Pointer and Arrays》的摘要。

1. 指针是一种数据类型,其存储的内容是其它变量的内存地址,指针的合法值是合法内存地址或0(NULL)。一元操作符&与*用于指针操作:

&:该操作符作用于变量,可理解为‘address-of’, &p即表示p的地址,由于改操作符的结果是得到一个内存地址,所以改操作符只能用于内存中的变量与数组元素,不能用于表达式(expression),常量和寄存器变量(register修饰的变量);

*:该操作符作用于指针,可理解为‘the value pointed by this pointer',*p即表示指针p所指对象的值。

2. 指针与数组:

在C中指针与数组有紧密的联系,如果在C中声明了一个数组int a[5],那么编译器会分配一段连续的内存用来存储这5个int数组变量,而a就是整个内存块的起始地址,即第一个变量a[0]的地址,所以a 与 &a[0]是等价的,可以将其赋值给任何int类型的指针,如:

#include <stdio.h>int main (int argc, char const* argv[]){    int a[5] = {1,2,3,4,5};    int *p = a;    if(a == &a[0]) printf("yes!\n");    printf("%d, %d, %d\n", a[0], *a, *p);    return 0;}
编译后结果为:

$ ./a.out yes!1, 1, 1

3. 指针地址运算:

1). 比较运算:因为指针的值是内存地址或0,所以算术比较运算符(==, !=, <=, >=, >, <)可用于指针,但通常只有在比较属于同一数组的元素地址时才有意义,可以通过此方式判断数组中元素的先后顺序;

2). 加法运算:指针可以与常量数值做加法运算,p + N表示当前p所指元素后面的第N个同类型元素地址,编译器通过指针p的数据类型知道每个元素的大小,例如int a[10], 那么a + 3表示&a[3];

3). 减法运算:指针与常量数值做减法运算时,其意义与加法类似但方向相反,例如int a[10], 那么(&a[9] - 5)表示&a[4];指针也可以与指针类型做减法,其结果表示两个地址之间包含了多少个同类型的变量:

#include <stdio.h>int main (int argc, char const* argv[]){    int a[10] = {1,2,3,4,5,6,7,8,9,10};    printf("%lu\n", (&a[9] - a));    return 0;}
结果为:

$ ./a.out 9
同理,减法运算通常也只有作用于属于同一数组中的元素时才有意义。

4. 字符串:

C中没有字符串类型,字符串即是char类型的指针,编译器内部表示为以字符'\0'结尾的字符串数组,字符'\0'用于帮助程序找到字符串的结尾。

5. 指针数组:即数组内每个元素是指向声明类型的指针,如char*argv[]中,argv[i]表示的是一个字符串。

6. 多维数组:以二维数组为例进行说明,int a[rows][cols]的声明中,cols必须显示声明大小,不管是否对该数组进行初始化;在函数的形参中,二维数组的cols也必须显示指定大小:

#include <stdio.h>#define ROW 2#define COLS 10void print_mularray(int [][COLS] , int len);int main (int argc, char const* argv[]){    int a[][COLS] = {        {1,2,3,4,5,6,7,8,9,10},        {-1,-2,-3,-4,-5,-6,-7,-8,-9,-10}    };    print_mularray(a, ROW);    return 0;}void print_mularray(int a[][COLS], int len){    int i = 0;    for(; i < len; i ++)    {        int j = 0;        for(; j < COLS; j ++)            printf("a[%d][%d]: %d\n", i, j, a[i][j]);    }}

那么为什么在形参中和在数组声明中,必须指定cols的大小呢?看如下示例:

#include <stdio.h>void f(int [][]);int a[][];int main (int argc, char const* argv[]){    return 0;}
编译时错误信息:

$ gcc mularray.c mularray.c:3:1: 错误:数组元素的类型不完全mularray.c:4:5: 错误:数组元素的类型不完全
编译器抱怨数组元素的类型不完全!首先看一下二维数组的内存布局:从程序视角我们可以将内存看成一个由字节组成的一维数组,其存储二维数组的方式是先存放第一列,再存放第二列并依次类推,如果我们有二维数组int a[2][5] = {{1,2,3,4,5}, {6,7,8,9,0}},那么其在内存中布局为:

1234567890那么如果在int a[rows][cols]的数组中求元素a[i][j]的值,其寻址方式为:a + i * sizeof(int [cols]) / sizeof(int) + j,即:a + i * cols + j;可知编译器必须知道cols的值,才能进行寻址,换句话说,编译器必须知道二维数组中每行的大小,才能正确的进行内存分配,而该值是通过cols来指定的。在上例int a[][]的声明中,编译器能够解析出数组a[]中的每个元素是一个int数组,但是不知道该数组的大小,所以抱怨类型不完全。再看下例:

#include <stdio.h>#define ROW 2#define COLS 10void print_mularraies(int (*)[]);int main (int argc, char const* argv[]){    int a[][COLS] = {        {1,2,3,4,5,6,7,8,9,10},        {-1,-2,-3,-4,-5,-6,-7,-8,-9,-10}    };    print_mularraies(a);    return 0;}void print_mularraies(int (*a)[]){    printf("%d", a[1][3]);    printf("%d, %d\n", (*a)[1], (*(a + 1))[1]);}
编译时错误信息:

$ gcc -Wall pointer.cgcc -Wall pointer.c pointer.c: 在函数‘print_mularraies’中:pointer.c:22:5: 错误:对未指定边界的数组的使用无效pointer.c:23:5: 错误:对未指定边界的数组的使用无效

原理类似,可以理解为:编译器在解析函数的形参int (*a)[]时,知道a是一个指针,指向一个数据类型是int []的对象,但是由于缺乏数组大小的声明,编译器无法获知每个对象占用多少内存,便无法进行a + 1这样的运算;同理a[1][3]等同于(*(a + 1))[3],错误相同。在函数的形参中声明数组的大小即可:

#include <stdio.h>#define ROW 2#define COLS 10void print_mularraies(int (*)[]);int main (int argc, char const* argv[]){    int a[][COLS] = {        {1,2,3,4,5,6,7,8,9,10},        {-1,-2,-3,-4,-5,-6,-7,-8,-9,-10}    };    print_mularraies(a);    return 0;}void print_mularraies(int (*a)[COLS]){    printf("%d\n", a[1][3]);    printf("%d, %d\n", (*a)[1], (*(a + 1))[1]);}

编译运行结果正确:

$ ./a.out -42, -2

7. 指针数组与多维数组:

同一数据类型的指针数组与多维数组在使用上类同,但在内存分配上并不一样。通过对多维数组的声明,编译器明确知道需要为其分配多少内存,如:

int [2][100];

编译器会为其分配sizeof(int) * 2 * 100个字节,而对于:

int *[100];

编译器只会初始化一个有100个int指针类型的数组,每个指针指向哪里则并没有定义。

一个字符串数组的内存布局示例如下:



8. 函数指针:

C中函数不是变量,但是可定义指向函数的指针,并将其用于赋值、传递给函数以及从函数返回,函数指针的声明格式为:

return-type (*) (arg-list)

使用方式见如下示例:

#include <stdio.h>int runner(int (*)(int, int), int, int);int sum(int, int);int sub(int, int);int main (int argc, char const* argv[]){    int i = 10, j = 5;    int r1, r2;    r1 = runner((int (*)(int, int))sum, i, j);    r2 = runner((int (*)(int, int))sub, i, j);    printf("sum: %d; sub: %d\n", r1, r2);    return 0;}int runner(int (*f)(int, int), int a, int b){    return (*f)(a, b);}int sum(int a, int b){    return a + b;}int sub(int a, int b){    return a - b;}
运行结果为:

$ ./a.out sum: 15; sub: 5

 ______________ < 本章终了 > --------------         \   ^__^          \  (oo)\_______              (__)\       )\/\                  ||------w |                  ||        ||








原创粉丝点击