针对在校大学生的C语言入门学习——指针和数组

针对在校大学生的C语言入门学习——指针和数组

存储地址的变量

  • 学习过C语言的人都知道,指针变量绝对是一个让人很头疼的学习难点。首先我们来分析一下“指针变量”这个词的中心词吧。中心词是“变量”,那么变量是用来干什么的呢?当然是存储数据以及做相应的运算。变量中存储的数据类型是由变量类型来决定的,比如整型变量存储整数、浮点型变量存储浮点数。那么指针变量存储什么呢?答案是地址!

地址的运算

  • 我们知道,变量本身并没有什么运算能力,所谓变量的运算一定是变量中存储的数据运算。比如int a;a++;这段代码是没有意义的,因为变量a中并没有数据,所以变量a的运算没有意义。
  • 所以我们在分析指针变量之前不妨先分析一下地址都有哪些运算,地址的运算方式就是指针变量的运算方式!地址的运算分两种,偏移运算和间接运算。
  • 地址偏移运算

  • 所谓的地址偏移就是对地址做加减法,注意地址没有乘除法!
int a;
printf("int %p %p\n",&a, &a+1);
char c;
printf("char %p %p\n",&c, &c+1);

前两行代码是定义整型变量a,并且打印a的地址和a的地址加1的值;后两行代码是定义字符变量c,并且打印c的地址和c的地址加1的置。下面是运行结果
在这里插入图片描述
每次运行的结果一定是不一样,因为地址是计算机根据运行状态分配的。但是a的地址和a的地址加1的值的差值是一定的。同理c的地址和c的地址加1的值的差值也是一定的。结果是十六进制的,可能新手同学看着会比较难受,但是不难计算出int类型的差值是4字节,char类型的差值是1字节。

  • 不同类型的变量地址做偏移运算的效果是不一样的,上面的例子中int类型的变量地址偏移一个单位是4字节,char类型的变量地址偏移一个单位是1字节。可以得到一个简单的计算公式:偏移字节数=类型长度*偏移单位。偏移运算非常重要,数组的访问元素时间复杂度之所以能达到 O(1)全靠地址偏移运算。
  • 地址间接运算

  • 间接运算符是*,可能有些同学发现了,这不是乘号吗?怎么就变成间接运算符了!*作为乘号的时候是二元运算,也就是在*的左右两边各有一个操作数,例如a*b;而*作为间接运算符是一元运算,只在*的右边有一个操作数,例如*p。这样就很好区分了。
  • 间接运算是如何运算的呢?举个生活中的例子,我们定外卖的时候是不是得把地址给商家呢,然后商家就可以根据我们留下的地址让骑手把外卖送给我们。这就是商家间接将外卖送给了我们。间接运算是一个意思,一旦地址加上间接运算符,那么就表示地址对应的变量了!
int a;
*&a = 10;
printf("%d\n",a);
  • 第二行代码中对a的地址做了间接运算,我们说一旦地址加上间接运算符,那么就表示地址对应的变量了。所以*&a就表示&a所对应的变量,也就是变量a。所以第二行代码就是对变量a赋值10。
  • 同学们思考一下就会觉得这间接运算真是徒增麻烦,想给a赋值的话直接赋值就好了,用得着这么折腾一下吗?没错,上面这段代码我只是为了演示一下间接运算的语法而已,这段代码根本没有任何逻辑意义。就好比我们在饭店点餐的时候商家一定是直接把美食送到我们面前,而不是找个骑手折腾一圈再间接的送给我们。间接运算一定是发生在不同的作用域中,很多时候是函数的调用时。具体操作下面会介绍。

指针变量声明方式

  • 因为地址只有两种运算,所以指针变量也是只有两种运算。接下来我们只要知道如何声明一个指针变量就大功告成了!
  • 指针变量的声明和之前学习过的变量声明不太一样,指针变量的声明属于复杂声明的范畴。关于复杂声明我暂时先不做详细的介绍,不是因为复杂声明难以理解,只是恐怕大家的知识储备还没到那个程度。感兴趣的同学可以去看丹尼斯里奇老先生的《C程序设计语言》。
  • 指针变量的声明使用符号*,简单的声明是在*的左边加上指向空间的数据类型。比如:
int *p;
char *p2;
  • 第一行的*说明p是一个指针变量,*号左边的int说明该指针变量指向的空间数据类型是整数类型,也就是说我们需要将int类型变量的地址赋值给指针变量p;第二行的*说明p2是指针变量,*左边char说明该指针变量指向的空间数据类型是字符型,也就是我们需要将char类型变量的地址赋值给指针变量p2.
int *p;
char *p2;
int a;
char b;
p = &a;//将整型变量的地址赋值给p,此时p指向a
p2 = &b;//将字符型变量的地址赋值给p2,此时p2指向b

声明和运算

  • 有一个容易被忽略的问题,一个符号出现在声明的语句和运算的语句中表示的含义是不一样的。符号出现在声明语句中时表示标识符的身份;符号出现在运算的语句中时表示变量的运算。就比如我们今天学习的*符号,出现在声明的语句中是表示标识符的身份是指针变量,而出现在运算的语句中时表示间接运算。
  • 顺便提一下,在C语言中表示标识符身份的符号还有[]和(),分别表示数组和函数。

指针变量的运算

  • 前面我们介绍了地址的偏移运算和间接运算,接下来我们看看指针变量的偏移运算和间接运算。
int a;
int *p = &a;//给变量p赋值a的地址,这里的*表示p的身份是指针变量
printf("int %p %p\n",p, p+1);//分别打印变量p中存放的地址和p中地址加1后的值
char c;
char *p2 = &c;//给变量p2赋值c的地址,这里的*表示p2的身份是指针变量
printf("char %p %p\n",p2, p2+1);//分别打印变量p2中存放的地址和p2中地址加1后的值

在这里插入图片描述
这个结果并不陌生,指针变量的偏移运算就是地址的偏移运算。

int a;
int *p = &a;//给指针变量p赋值a的地址,这里的*说明p的身份是指针变量
*p = 10;//间接运算,这里的*是间接运算符
printf("%d\n",a);

结果输出是10,和地址的间接运算一样。

指针变量的使用方式

  • 指针变量有4种常见使用方式:函数参数、指向堆空间变量、指向函数、指向字符串常量。
  • 我们今天只研究指针做函数参数。指针变量做函数的参数一般是为了做“输出参数”。什么是输出参数呢?比如A函数调用B函数,需要B函数对A函数中的变量进行写操作。请看下面代码
void swap(int *a, int *b)
{
    
    
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main(void)
{
    
    
    int m = 10, n = 20;
    swap(&m, &n);
    printf("m = %d  n = %d\n", m, n);
    return 0;
}

输出结果是m = 20 n = 10。swap函数的功能是将main函数中的m和n的值进行交换。如何做到的呢?main函数在调用swap的时候,swap函数的参数a指向了main函数中的变量m,参数b指向了main函数中的变量n。然后在swap函数中使用间接运算将参数a、b指向的变量值进行了交换。在swap函数中*a就表示main函数中的m,*b就表示main函数中的n。

  • 因为main函数和swap函数是两个函数,所以是两个独立的作用域,那么swap函数中想对main函数中的m、n直接赋值是不可能的,所以需要使用地址传递,然后根据m、n的地址间接对m、n赋值。既然需要传递地址,那么能存放地址的变量就只有指针变量。
  • 做函数参数的时候还有一种情况就是void*做泛型参数,我们今天就先不细聊了。

数组

  • 数组的语法是很容易学会的。那么什么时候我们使用数组呢?还是举一个生活中的例子,同学们应该有平时抽烟的吧,你们买烟的时候什么会买一包烟,什么时候会买一条烟呢?
  • 我的定义就是当我们需要使用若干个类型相同,逻辑意义有相同的变量时,可以定义一个数组。
  • 比如我要表示班级里3个同学的成绩,我可以如下写
int score1, score2, score3;
  • 这样写看起来也挺好的,但是假如是300个同学的成绩呢?这个时候我们会发现表示成绩的变量类型都是一样的,而且逻辑意义也都是表示成绩。所以可以定义成数组如下
int scores[3];

数组和指针

  • 提到数组往往少不了指针(我们一般把指针变量简称“指针”)。他们有什么联系呢?首先,数组的名字是数组的首元素地址。好了,有地址的地方就有指针。其次,数组的元素在内存中是连续的,说白了就是地址是挨着的。所以提到连续大家会想到什么?当然是地址的偏移运算了。关于指针的数组的代码举例大家可以参考上一次文章中(针对在校大学生的C语言入门学习——函数)的函数封装部分。
  • 最后还想说一个大家对数组经常会有的一个毫无意义的操作,请看下面代码
    int arr[10];
    printf("arr's len is %d", sizeof(arr));
  • 这段代码使用sizeof运算符计算数组占内存字节数,为什么说没有意义呢?因为sizeof中只有传递数组名时才能计算出正确结果,而我们能够使用数组名的函数一定是定义数组的函数。我们都知道定义数组的时候必须指定长度或者初始化。无论哪种方式我们都一定知道数组占多大内存,何必再去计算呢。
  • 如果数组以地址传递的方式传递给某个函数了,就比如上一篇文章里的函数void sortArr(int* arr, int len)。如果sizeof(arr)那么得到的结果一定是4字节,因为指针变量存放地址,地址就是4字节。声明:32位编译器中地址就是4字节。
  • 也就是说在其不能确定数组长度的函数中使用sizeof有计算不出数组长度,因此sizeof(数组名)这种用法没有实际使用意义。
  • 今天重点和大家聊聊指针的基本用法,至于函数指针、多维数组等等,我后面还会给大家详细介绍。

猜你喜欢

转载自blog.csdn.net/seamancsdn/article/details/112387908