老规矩,在每节课上课之前,我们先来回顾一下上节课的内容,上节课我们讲了函数,数组,局部变量和全局变量,以及C语言中的存储类。

一、函数

函数如何定义?函数如何声明?声明的作用是什么?函数如何调用?函数的传值调用的执行机制又是什么?这些东西,希望大家能够全部理解。

二、数组

一维数组如何定义?二维数组如何定义?一维数组在内存的存储方式是怎样的?一维数组和二维数组如何初始化?怎么去调用一维数组和二维数组的元素?数组和函数结合,有什么用法?数组和指针结合又有什么用法?

三、存储类

C语言中存储类关键字有哪几个?auto,register,static,extern这几个存储类关键字又有什么作用?C语言的局部变量和全局变量的区别又是什么?函数的形式参数生命周期是什么范围?

以上就是我们上节课讲述的内容。今天我们接着讲剩下一部分,指针,函数指针和回调函数,字符串。

一、指针

很多初学者,总是觉得指针很难,难以理解,看到指针就觉得头痛。其实,如果真正理解了指针,你就会觉得,指针是个非常好用的工具,能帮助我们简化很多代码。今天,我们就来详细的讲一讲,指针是什么?

1.指针的概念

我们在之前已经讲过,计算机的内存存储,是由一个个存储器单元构成的,每个存储器单元能够存储8位数据,为了区分每个不同的存储器单元,所以我们给每个存储器都进行一个编号,大家还记得吗?这个编号就叫做地址,我们可以通过地址直接对某个或者某几个存储器单元进行读写过程。还有,我们在讲运算符一节时,我们跳过了两个运算符没有进行详细讲解,大家都还记得吗?哪两个运算符?大家回去看看就知道了,一个运算符是*,这个运算符用于在指定地址里取值出来,还有一个运算符是&,这个运算符用于取变量地址。

我们首先来介绍取地址运算符&和取地址值的运算符*,我们来看个例子:

#include <stdio.h>int main(void)
{int a = 1;printf("&a = %p\r\n",&a);printf("*(&a) = %d\r\n",a);return 0;
}

在这个例子中,我们定义了一个变量,名字叫做a,这个变量的类型是int类型。我们之前说过,定义变量的实质,就是在内存中寻找一块存放地址。我们用变量名,就能够轻易操控这块地址的内容。我们这里的变量a,它有一个在内存中存放的地址,我们怎么获取它到底存放在哪儿的呢?很明显,我们这里使用了一个&符号,这个符号就叫做取地址符号,用于取某个变量的存放地址。在下列输出中,很明显能看到,变量a存放的地址是0x000000000061FE1C,因为本人是64位操作系统,所以系统的地址是64位的,也就是八个字节。通过&运算符,我们就获得了某个变量存储的位置,也就是地址。我们再来看,在下面一句话中,我们使用了另一个运算符*,我们首先通过&运算符得到了a变量的地址,然后使用*运算符,得到了地址里的内容。由此可以看出,*运算符,叫做取值运算符,用于获取某个地址里的内容。这里我们的内容就是我们定义变量时的值。

好了,&运算符和*运算符我们现在已经讲完了。不是说讲指针吗?怎么还不讲呢?其实,我们已经讲完了,指针实际上就是内存地址,我们利用不同的地址,就能访问不同的内存空间。这就是指针的应用,通过物理地址的访问,对我们的变量进行读写。

2.指针变量

在之前,我们已经介绍了C语言中的一些内置变量,整形变量,浮点型变量,数组类型。今天我们又要拓展另外一种变量,叫做指针变量。众所周知,我们之前定义的变量,里面存储的都是实际的变量值;但是,指针变量在C语言中是一种特殊的存在,它保存的不是变量的值,而是变量的地址。也就是说,用指针变量,能够保存变量地址,我们就能用指针变量进行更灵活的操作。首先我们来讲一下指针变量的定义和使用,来看一个例子:

#include <stdio.h>int main(void) {int a = 100;int *p;p = &a;printf("&p = %p\r\n", &p);printf("p = %p\r\n", p);printf("*p = %d\r\n", *p);return 0;
}

在这个例子中,我们定义变量时,出现了一种新的定义方式: int *p,这种定义方式,就是用于定义指针变量。我们在指定变量类型后,添加一个*号,代表我们定义的这个变量为指针变量,指向的数据类型是你定义的类型。指针变量也有对应的数据类型,定义了类型之后,这个指针变量,只能指向相同类型的变量地址。不能让其指向其他类型的变量,否则编译器会报错。为什么不行,用一张图就能搞懂,如下图所示:

图中清晰的展示了为什么,使用指针变量指向不同类型是不允许的,指针变量应该指向对应类型的变量地址。

3.NULL指针

NULL指针又被称为空指针,说明指针现在不指向任何地方。为什么要有NULL指针呢?我们说了,我们在定义变量时候,必须要对变量进行初始化,除非你有十足的把握,保证定义变量的数据为你想要的数据。如果不对变量的值进行初始化,则变量的值是不可预测的,如果我们定义了一个指针,则指针的指向也是不可预测的,我们将其称为野指针。野指针,就是指向不定的指针变量,使用野指针有很大可能会造成程序的崩溃。所以,现在有个特殊的指针,就叫做NULL指针,即空指针,表示这个指针变量不指向任何地方,实际上,这个空指针的地址为0,也就是说,我们把地址为0的指针也称为空指针。NULL指针的出现,就是为了在你初始化的时候,如果不确定指针的指向,我们就把NULL指针赋予给它,防止后面的程序设计疏忽使用野指针,导致程序崩溃。下面是赋予NULL指针的一个例子:

int *p = NULL;

在这里,我们定义了一个p指针,指向int类型变量,初始化,我们让这个指针指向NULL,代表现在这个指针不指向任何地方,是个空指针。

4.指针变量的算术运算

我们定义的整形变量、浮点型变量,都可以进行加减乘除的运算,那指针变量可以进行算术运算吗?当然可以,但是指针变量的运算只有加减运算,没有其他的运算。我们来看看指针运算的例子:

#include <stdio.h>int main(void)
{int i = 0;int *p;int a[10] = {1,2,3,4,5,6,7,8,9,10};p = a;for(i=0;i<10;i++){printf("*p = %d\r\n",*p);p++;}return 0;
}

执行结果如下图所示

通过以上一个例子,充分展示了指针的加减运算,在上面的例子中,我们定义了一个指针p,其指向的类型为int类型,那么p一次所指向的是4字节内存空间。这里,初始化,我们让p指向int类型的数组a,那为什么可以直接使用p = a;语句呢?我们在这里解释一下,C语言中我们所定义的数组,其实也是一个指针,数组名其实就是一个指针,也就是一个地址,只不过数组名属于指针常量,一旦定义了数组后,这个数组指针的指向就是确定的,不能再发生改变。因此我们在这里可以直接将a(也就是数组名)赋值给对应类型的指针变量。也就是说,现在p指向a的第一个元素,在for循环中,每次打印出p指针指向地址的值,然后进行一次p++操作,也就是说,让我们的指针p指向下一个四字节地址,这样,每次指向一次p++操作后,p就指向a数组的下一个元素。我们使用p--操作就是指向数组上一个元素。这个就是指针的算术运算,只能进行加减运算,当然,不限于p++,我们还可以这样执行p += 9;也就是说,p指针现在的指向向后移动九个单位(这里的单位要根据数据类型决定)

5.指针数组

什么是数组?数组就是保存多个同一类型变量的数据类型,那指针数组又是什么呢?顾名思义,数组保存的数据类型是指针类型,我们把这种数组叫做指针数组。指针数组定义方式如下:

int* a[10];

这样我们就定义了一个数组,这个数组比较特殊,它保存的数据类型是指针类型的数据,这个指针类型指向的是int类型的变量。这个数组一共能够保存10个指针变量。

同样,指针数组也可以进行初始化,初始化的时候注意,要把对应的地址赋予给它才行。

6.函数的地址调用

我们之前说了,函数的参数执行机制,是在进入函数前,将参数进行拷贝一份,然后将代入的值给拷贝的参数。这种方式,叫做传值调用。我们这里的地址调用,其实就是将指针作为函数的参数。我们同样拿上次的两数交换的函数做例子:

#include <stdio.h>void swarp(int*,int*);int main(void)
{int a = 10,b = 20;printf("交换前: a = %d,b = %d\r\n",a,b);swarp(&a,&b);    printf("交换后: a = %d,b = %d\r\n",a,b);return 0;
}void swarp(int* a,int* b)
{int temp;temp = *a;*a = *b;*b = temp;}

上述代码执行结果如下图所示:

这次执行结果,怎么和我们上次讲的执行结果不一样呢?让我们来看看swarp函数是如何定义的,swarp函数,传入的参数是什么?是指针,也就是地址,我们将各个变量的地址传递进去。那函数中的运算,针对的都是指定地址里的数据进行运算。所以,在执行完之后,外部代入的变量,它们的地址是不变的,而在函数内,对应地址里的值已经发生了改变,这才达到了两个数交换的目的。这就是传值调用和地址调用的区别,请大家,务必搞清楚这一点。

同时,上述例子,也讲述了一种指针的用法,就是将指针作为函数的参数代入,接下来我们讲另一种指针的用法。

7.指针作为函数的返回值

指针也是一种数据类型,当然可以作为函数的返回值,我们来看看,一个函数如何返回一个指针变量出来。

int* function(void)
{int* p = NULL;return p;
}

这是一个简单的例子,如果想要返回指针,我们直接在返回的数据类型后加一个*就好,这里的*号为标识符号,表示返回的数据类型是指针类型的数据。这个函数返回了一个NULL指针。

8.函数指针与回调函数

指针在C语言的应用中及其广泛,原因之一也是因为C语言中有函数指针。那什么叫做函数指针呢?函数指针指的是,一个指针指向一个函数,我们调用这个指针的时候,会自动执行这个函数的内容。我们来看个函数指针的例子:

#include <stdio.h>int (*point_add)(int *, int *);
int add(int *, int *);int main(void) {int result, a, b;point_add = add;a = 10;b = 20;result = point_add(&a, &b );printf("result = %d\r\n", result);return 0;
}int add(int *a, int *b) {return *a + *b;
}

在这个例子中,我们编写了一个add加法函数,其两个参数均为int类型的指针,返回值为int类型的数据。但是,我们来看主函数,我们并没有用到add加法函数,而是用到了point_add函数,我们暂时先把它说成函数,我们先来看看结果。

结果等于a和b的和,同样执行的是加法运算,为什么呢?这是因为,我们在这里,定义了一个函数指针,point_add,这个指针指向了add函数,我们在调用这个指针的时候,就自动执行add函数中的内容。那这个指针指向的是什么呢?我们来看指向的函数的参数,两个都为int*类型,返回值为int类型。这个不正好与我们编写的add函数的参数和返回值都是一样的吗?的确是这样的,想要定义一个函数指针,首先需要确定,我们们需要这个指针指向哪个函数,把指向的函数的参数类型以及返回值类型都确定,然后按照如下格式声明

return_type (* name)(par_type,.....);

return_type为我们需要指向函数的返回值;

name为我们需要定义函数指针的名字;

par_type为函数的参数列表,一定要和指向的函数参数类型一一对应;

另外别忘了,在函数名字前加一个*符号,然后将*和名字一起用小括号括起来。

这样,我们就定义了一个函数指针,用于指向某个函数。

那么函数指针有什么用呢?我们来看一个简单的例子:

#include <stdio.h>float (*pointToType)(float, float );float add(float a, float b);
float decrease(float a, float b);
float multiply(float a, float b);
float divide(float a, float b);int main(void) {float input_a, input_b;char input_type;float result;printf("请按格式输入: 数字a,数字b,执行符号\r\n");scanf("%f,%f,%c", &input_a, &input_b, &input_type);if (input_type == '+') {pointToType = add;} else if (input_type == '-') {pointToType = decrease;} else if (input_type == '*') {pointToType = multiply;} else if (input_type == '/') {pointToType = divide;}result = pointToType(input_a, input_b);printf("result = %f\r\n", result);return 0;
}float add(float a, float b) {return a + b;
}float decrease(float a, float b) {return a - b;
}float multiply(float a, float b) {return a * b;
}float divide(float a, float b) {if (b == (0.0f)) {return 0;}return a / b;
}

这个例子,充分说明了,函数指针的用处,在这个例子中,虽然我们只调用了一个函数指针,但是函数指针的指向是可以改变的,我们可以在程序中,随意改变函数指针的指向,从而达到一个函数名就能执行不同的逻辑的目的。这在C语言中是可以相当灵活的,因为我们不再拘泥于,逻辑代码写完后,所有的函数执行代码都定死了,我们可以通过函数指针,灵活的切换函数的执行代码。以下是我们的执行结果。

另外,函数指针和普通指针一样,可以当作函数的参数使用。我们举一个例子

void function(int (*f)(int,int))
{}

这个函数中,我们只有一个参数,那就是一个函数指针,参数名叫做f,指针指向的是一个由两个int类型参数和int类型返回值的函数。我们把这种用法,叫做回调函数调用,就是说,我们在另一个函数中,调用了函数指针参数指向的函数,回调函数的使用,在嵌入式开发应用及其广阔。请务必掌握函数指针的用法。

      二、字符串

字符串的用法相对来说比较简单,大家可以看C 字符串 | 菜鸟教程 (runoob.com)自行理解。字符串,实际上就是一个字符型数组。

(四)C语言零基础入门 --- C语言之入门课程相关推荐

  1. c语言零基础自学,c语言零基础入门 该如何学习

    原标题:c语言零基础入门 该如何学习 ​零基础学习C语言该从哪里开始学习呢?在学习之前你可以先问自己,为什么我要学C语言?是为了应付考试,还是为了应聘,还是为了提高自己的编程能力.如果你以后想要长期致 ...

  2. 单片机C语言零基础入门05 - 逻辑运算

    硬件家园单片机C语言零基础入门资料汇总链接: https://mp.weixin.qq.com/s/hMTreNUX_V90461tvALjJA ​ 一.逻辑与或非 基础理论: 逻辑与或非,运算对象是 ...

  3. C语言零基础入门:孙海洋-C语言程序设计-清华大学出版社

    C语言零基础自学入门:**<C语言程序设计>-孙海洋-清华大学出版社 为方便同学们学习,在中国大学慕课网 免费提供全套视频讲解. 讲解清晰易懂,便于自学,几乎每个知识点均有对应例程,注重程 ...

  4. c语言word类型_C语言零基础入门—数据类型

    ​C语言零基础入门--3.数据类型 这一节的主要任务是 讲解C语言的 数据类型 . 1. 数据类型 1.1 何为数据类型 数据类型,其实在我们生活中经常被使用,但是在编程语言中被重新定义了一下,并且加 ...

  5. rust放置木箱转向_[易学易懂系列|rustlang语言|零基础|快速入门|(17)|装箱crates]...

    [易学易懂系列|rustlang语言|零基础|快速入门|(17)|装箱crates] 实用知识 装箱crates 我们今天来讲讲装箱技术crates. 什么是crates? 英语翻译是: 英 [kre ...

  6. c语言int转字符串_C语言零基础入门-指针-05

    C语言零基础入门-指针-05 本节要点: 1,字符指针. 2,字符串指针. 3,指针的参数传递 4,多重指针 01. 字符指针 这里的定义与前面的基本一样,所谓的字符指针就是这个指针指向的是一个字符型 ...

  7. 零基础学习C语言如何入门(内附工具书推荐+视频教程)

    C语言同C++.Python等都是通用性的编程语言,它们不针对某一个特定的方向,能做的事情很多.C语言主要用于底层开发,Windows.Linux.Unix 等操作系统的内核90%以上都使用C语言开发 ...

  8. 零基础学习C语言,怎么入门?

    C语言同C++.Python等都是通用性的编程语言,它们不针对某一个特定的方向,能做的事情很多. C语言主要用于底层开发,Windows.Linux.Unix 等操作系统的内核90%以上都使用C语言开 ...

  9. c语言零基础入门(经验总结)

    作者本人也是计算机小白一枚,作为一名未来的电脑工作者,最开始学的就是c语言.将其作为一门了解编程,认识算法的学科再好不过.在本篇文章中不会具体涉及过多的C语言写法注意事项,主要是想谈一谈这半年中我的一 ...

  10. 最新C语言零基础入门(带课件+源码)

    C语言零基础入门 带课件+源码 下载地址:百度网盘

最新文章

  1. 对文本框只允许输入数字
  2. python网络编程--UDP客户端
  3. Java的多进程运行模式分析
  4. Java笔记03-Constructor Override
  5. Java IdentityHashMap put()方法与示例
  6. 统计学习方法笔记(李航)———第四章(朴素贝叶斯法)
  7. 怎么清除远程计算机的用户名,Windows8系统如何清除“远程桌面连接”登录历史痕迹...
  8. NLP硬核入门-条件随机场CRF
  9. 如何转型成为一家真正发挥大数据作用的 “数据驱动型公司”?
  10. java多线程学习-实例变量和线程安全
  11. 办公软件excel表格_软件和Excel表格管理仓库的区别?
  12. NLTK09《Python自然语言处理》code08 分析句子结构
  13. 我们的小太阳钟汉良,不老男神,希望你继续帅下去
  14. 上海市居住证办理材料及流程
  15. SpringMVC个人理解(downpour 的SpringMVC深度探险的个人整理)
  16. html table 表格脚,HTML表格table
  17. 2021-2025年中国脚手架系统行业市场供需与战略研究报告
  18. Day14【元宇宙的实践构想03】—— 元宇宙的资产观(NFT、数字资产、虚拟地产、与现实世界资产关系)
  19. 现在主流免杀是源码免杀
  20. 每周市场观察:多头转空头? | TokenInsight

热门文章

  1. 推荐几个2023年比较好用的youtube转换器
  2. T3:LOJ2332「JOI 2017 Final」焚风现象.cpp
  3. JS流程控制语句 反反复复(while循环) 和for循环有相同功能的还有while循环, while循环重复执行一段代码,直到某个条件不再满足。...
  4. 理解热插拔技术:热插拔保护电路设计过程实例
  5. 分析肖特基二极管的优势与结构应用
  6. 论文查重一般包括哪些部分呢?
  7. 2022年天津最新建筑八大员(电气)模拟考试题库及答案
  8. Android之MVVM简单例子
  9. 解决公众号网页 微信免登录 重定向 地址 只能带一个参数问题
  10. qrCode生成二维码图片