C语言指针

二维数组和指针

int a[3][4] = { {1,3,5,7},{9,11,13,15},{17,19,21,23} }; // 设该数组的地址为2000

a是二维数组名,a数组有3个元素,每个元素都是一个一维数组,因此又称a数组有3个行元素。

  • 每个行元素是一位数组,包含4个元素(又称列元素)
  • a+0是第0行的地址,a+1是第1行地址,那么a+1就等于2000+16 = 2016
  • a[0],a[1]是一维数组名,等同于一个一维数组的首地址,即&a[0][0],&a[1][0],又a[0],a[1]还可以用*(a+0), *(a+1)表示
  • 单从值来看,a+1是等于*(a+1),同时他两也都是地址,但a+1是指向第一行的地址,*(a+1)是第一行所在一维数组的整型变量的地址,就是说指针类型不一样,因此a+1+1就是2032, *(a+1) + 1则是2020
  • 因此要用一个指针指向一个一维数组,那么定义int (*pt)[4];,此时pt就可进行赋值pt = a[0];
  • a和a+1是指向行的指针,在他们前面加*,变成*a和*(a+1)后就变成了列指针,即`*a + i`就可选中a列,而列指针前面加&能变回行指针,如&a[1]
  • 不要把&a[i]简单理解为a[i]元素的存储单元的地址,因为不存在a[i]这样一个实际的数据存储单元,他只是一种地址的计算方法。而在一维数组b中,b+i所指的是一个实际数组元素的存储单元
表达式基类型
a+i2000 + 16 *i一维数组
a[i]2000 + 16 *iint
*(a+i)2000 + 16 *iint
&a[i]2000 + 16 *i一维数组
&a[i][0]2000 + 16 *iint

用这种方式定义的数组在内存中其实就是按行优先存放,即用连续的12个整型int的空间存放该数组,因此计算a[i][j]的相对位置所用的公式为i*4 + j

用这种方式定义数组时,若想把数组作为参数放在另一个函数中处理,有两个方法

  • 传这个二维数组的第一个int元素的地址p和长度n,然后通过*(p+i)访问
void visit(int *p, int n){
    for(int i = 0; i < n; ++i)
        printf("%d", *(p+i)) }
visit(a[0], 12);
  • 传这个二维数组的首行地址,然后传行数列数
void visit(int (*p)[4], int m, int n){
    for(int i = 0; i < m; ++i){
        for(int j = 0; j < n; ++j){
            printf("%d ", *(*(p+i)+ j) );
        } 
    }
}
visit(a, 3, 4);

还有另一种定义二维数组的方法,即动态分配

int m = 3, n = 4;
int **a = (int**)malloc( sizof(int*) * m);//先把a定义成指针的指针,a指向一个一维数组的地址,其中这个一维数组是装m个int类型的指针
for(int i = 0; i < m; ++i) {
    a[i] = (int*)malloc(sizeof(int) * n);//每个int类型指针都指向各种的一维数组,该一维数组是装n个int类型
    for(int j = 0; j < n; ++j) a[i][j] = 0;//初始化
}

这种动态分配的方法在内存上,与上面int a[3][4]不同,这种方法同一行内的元素连续,但不同行之间地址随机

字符指针变量和字符数组

用字符数组和字符指针变量都能实现字符串的存储和运算,但他们二者有区别

  • 字符数组名是个常量,它只表示该字符数组的起始地址,不可被赋值,字符指针变量也表示某个字符的地址,但他本质上还是个指针变量,可以赋值
char *a;
a = "hello world!";//正确
char str[14];
str[0] = 'h';// 正确,可以修改数组内的某个元素
str = "hello world!";// 错误,str是数组名,是常量地址,不能被赋值
char str[14] = "hello world!";// 要么就一开始的时候就初始化
  • 字符数组中各元素的值是可以改变的(可再赋值),但字符指针变量指向的字符串常量中的内容是不可以再赋值
char a[] = "house";// a数组初始化,每个存储单元存一个字符
char *b = "house";// b指针指向字符串常量的第一个字符
a[2] = 't';//正确,可正常修改
b[2] = 't';//非法,字符串常量不能改变

函数指针

如果在程序中定义了一个函数,在编译时会把函数的源代码转换为可执行代码并分配一段存储空间,这段内存空间有一个起始地址,也称为函数入口地址。每次调用函数时都从该地址入口开始执行此段函数代码。函数名就是函数的指针,代表函数的起始地址。 int (* p)(int, int) 上述语句即定义p是一个指向函数的指针变量,它指向的函数类型为:返回类型为int的,有两个int型参数的函数,该类型可用int (*)(int, int)表示

——C程序设计第五版
  • 解读:p先与 *结合,表明p是一个指针,然后后面的()表面他是一个函数指针;若漏掉 *p两边的括号,则p会先和()结合,表明p是一个有两个int参数的函数,并且该函数返回一个整型指针
  • 用法: int result = ( *p)(参数1, 参数2);,也相当于int result = p(参数1, 参数2);
int myMax(int a, int b){
    if (a >= b) return a;
    return b;
}
int res01 = myMax(1,2);
int (*p)(int,int) = myMax;
int res02 = (* p)(1,2);
printf("res01=%d,res02=%d\n", res01, res02);
  • NOTE: 函数指针不能进行算术运算

指针数组

指针数组:其元素均为指针类型 int * p[4]; 解读:p先与[4]结合,表明p是一个数组,然后int *指明了它是一个元素类型为整型指针的数组 易混:int (* p)[4]表明p是一个指针,它指向有四个元素的一维数组

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注