搜索

C语言教程

编程基础

通俗地理解什么是编程语言 C 语言究竟是一门怎样的语言? C 语言和 C++ 到底有什么关系? 学了 C 语言到底能做什么,能从事什么工作? 二进制、八进制和十六进制 不同进制之间的转换 数据在内存中的存储 载入内存,让程序运行起来 ASCII 编码,将英文存储到计算机 GB2312 编码和 GBK 编码,将中文存储到计算机 Unicode 字符集,将全世界的文字存储到计算机 程序员的薪水和发展方向大全 不要这样学习 C 语言,这是一个坑! 明白了这点才能学好编程,否则参加什么培训班都没用

C 语言初探

第一个 C 语言程序 选择正确的输入法,严格区分中英文 什么是源文件? 什么是编译和链接? 主流 C 语言编译器有哪些? 什么是 IDE(集成开发环境)? 什么是工程/项目? 哪款 C 语言编译器(IDE)适合初学者? 如何在手机上编写 C 语言代码? C 语言的三套标准:C89、C99 和 C11 C 语言为什么有那么多编译器? 程序安装是怎么回事? 制作安装包,让用户安装程序 C 语言程序的错误和警告 分析第一个 C 语言程序 C 语言代码中的空白符 彩色版的 C 语言,让文字更漂亮 一个真正带界面的 C 语言程序

变量和数据类型

大话 C 语言变量和数据类型 在屏幕上输出各种类型的数据 C 语言中的整数(short,int,long) 二进制数、八进制数和十六进制数 C 语言中的正负数及其输出 整数在内存中是如何存储的,为什么它堪称天才般的设计 整数的取值范围以及数值溢出 C 语言中的小数(float,double) 小数在内存中是如何存储的,揭秘诺贝尔奖级别的设计(长篇神文) 在 C 语言中使用英文字符 在 C 语言中使用中文字符 C 语言到底使用什么编码?谁说 C 语言使用 ASCII 码,真是荒谬! C 语言转义字符 C 语言中的几个重要概念 C 语言加减乘除运算 C 语言自增(++) 和自减(--) 变量的定义位置以及初始值 运算符的优先级和结合性 C 语言数据类型转换

C 语言输入输出

数据输出大汇总以及轻量进阶 在屏幕的任意位置输出字符,开发小游戏的第一步 使用 scanf 读取从键盘输入的数据 从键盘输入字符和字符串 进入缓冲区(缓存)的世界,破解一切与输入输出有关的疑难杂症 结合缓冲区谈 scanf 函数,那些奇怪的行为其实都有章可循 清空(刷新)缓冲区,从根本上消除那些奇怪的行为 scanf 的高级用法,原来 scanf 还有这么多新技能 C 语言模拟密码输入(显示星号 ) 非阻塞式键盘监听,用户不输入数据程序也能继续执行

循环结构和选择结构

else 语句 C 语言关系运算符 C 语言逻辑运算符 case 语句 C 语言条件运算符 C 语言 while 循环 C 语言 for 循环 C 语言跳出循环 C 语言循环嵌套 对选择结构和循环结构的总结 谈编程思维的培养,初学者如何实现自我突破(非常重要) 写一个内存泄露的例子,让计算机内存爆满

C 语言数组

什么是数组 C 语言二维数组 判断数组中是否包含某个元素 C 语言字符数组和字符串 字符串的输入和输出 C 语言字符串处理函数 C 语言数组是静态的,不能插入或删除元素 C 语言数组的越界和溢出 C 语言变长数组:使用变量指明数组的长度 对数组元素进行排序 对 C 语言数组的总结

C 语言函数

什么是函数? C 语言函数定义 C 语言函数的形参和实参 C 语言函数的返回值 C 语言函数的调用(从中发现程序运行的秘密) 函数声明以及函数原型 全局变量和局部变量 C 语言变量的作用域 C 语言块级变量 C 语言递归函数(带实例演示) 中间递归函数(比较复杂的一种递归) 多层递归函数(最烧脑的一种递归) 递归函数的致命缺陷:巨大的时间开销和内存开销(附带优化方案) 忽略语法细节,从整体上理解函数

预处理命令

什么是预处理命令? #include 的用法 C 语言宏定义 C 语言带参数的宏定义 带参宏定义和函数的区别 宏参数的字符串化和宏参数的连接 C 语言中几个预定义宏 C 语言条件编译 #error 命令,阻止程序编译 C 语言预处理命令总结

指针

1 分钟彻底理解指针的概念 指针变量的定义和使用 C 语言指针变量的运算 数组指针(指向数组的指针) 字符串指针(指向字符串的指针) C 语言数组灵活多变的访问形式 指针变量作为函数参数 C 语言指针作为函数返回值 二级指针(指向指针的指针) 空指针 NULL 以及 void 指针 数组和指针绝不等价,数组是另外一种类型 数组到底在什么时候会转换为指针 指针数组(数组每个元素都是指针) 一道题目玩转指针数组和二级指针 二维数组指针(指向二维数组的指针) 函数指针(指向函数的指针) 只需一招,彻底攻克 C 语言指针,再复杂的指针都不怕 main() 函数的高级用法:接收用户输入的数据 对 C 语言指针的总结

结构体

C 语言结构体 C 语言结构体数组 C 语言结构体指针 C 语言枚举类型 C 语言共用体 大端小端以及判别方式 C 语言位域 C 语言位运算 使用位运算对数据或文件内容进行加密

重要知识点补充

typedef 的用法 const 的用法 C 语言随机数

文件操作

C 语言中的文件是什么? C 语言打开文件 文本文件和二进制文件到底有什么区别? 以字符形式读写文件 以字符串的形式读写文件 以数据块的形式读写文件 格式化读写文件 随机读写文件 C 语言实现文件复制功能 FILE 结构体以及缓冲区深入探讨 获取文件大小(长度) 插入、删除、更改文件内容

C 语言调试

调试的概念以及调试器的选择 设置断点,开始调试 查看和修改变量的值 单步调试(逐语句调试和逐过程调试) 即时窗口的使用 查看、修改运行时的内存 有条件断点的设置 assert 断言函数 调试信息的输出 VS 调试的总结以及技巧

只需一招,彻底攻克 C 语言指针,再复杂的指针都不怕

前面我们讲解了指针数组、二维数组指针、函数指针等几种较为复杂的指针,它们的定义形式分别是:
int *p1[6];  //指针数组
int *(p2[6]);  //指针数组,和上面的形式等价
int (*p3)[6];  //二维数组指针
int (*p4)(int, int);  //函数指针
我相信大部分初学者对上面几种形式的指针都非常迷惑,不知道该从哪里入手去理解,为什么 p1、p2 是数组,而 p3 却是指针呢,它们仅仅是一个括号的区别。

指针是 C 语言中最强大最灵活的一部分,也是最难以理解的一部分,它是学习 C 语言的重点,没有学会指针就无从谈学会 C 语言。如果大家觉得上面几种形式的指针还能勉强接受,那么下面两个指针是不是让人抓狂呢?
char *(* c[10])(int **p);
int (*(*(*pfunc)(int *))[5])(int *);
只要找到了窍门,再复杂的指针也是可以理解的,这节我们就来戳破这层窗户纸!

C语言标准规定,对于一个符号的定义,编译器总是从它的名字开始读取,然后按照优先级顺序依次解析。对,从名字开始,不是从开头也不是从末尾,这是理解复杂指针的关键!

对于初学者,有几种运算符的优先级非常容易混淆,它们的优先级从高到低依次是:
学会了“绝杀招式”,接下来我们就由浅入深,逐个击破上面的指针定义。

1) int *p1[6];

从 p1 开始理解,它的左边是 *,右边是 [ ],[ ] 的优先级高于 *,所以编译器先解析p1[6],p1 首先是一个拥有 6 个元素的数组,然后再解析int *,它用来说明数组元素的类型。从整体上讲,p1 是一个拥有 6 个 int * 元素的数组,也即指针数组。

2) int (*p3)[6];

从 p3 开始理解,( ) 的优先级最高,编译器先解析(*p3),p3 首先是一个指针,剩下的int [6]是 p3 指向的数据的类型,它是一个拥有 6 个元素的一维数组。从整体上讲,p3 是一个指向拥有 6 个 int 元素数组的指针,也即二维数组指针。
为了能够通过指针来遍历数组元素,在定义数组指针时需要进行降维处理,例如三维数组指针实际指向的数据类型是二维数组,二维数组指针实际指向的数据类型是一维数组,一维数组指针实际指向的是一个基本类型;在表达式中,数组名也会进行同样的转换(下降一维)。

3) int (*p4)(int, int);

从 p4 开始理解,( ) 的优先级最高,编译器先解析(*p4),p4 首先是一个指针,它后边的 ( ) 说明 p4 指向的是一个函数,括号中的int, int是参数列表,开头的int用来说明函数的返回值类型。整体来看,p4 是一个指向原型为int func(int, int);的函数的指针。

4) char *(* c[10])(int **p);

这个定义有两个名字,分别是 c 和 p,乍看起来 p 是指针变量的名字,不过很遗憾这是错误的。如果 p 是指针变量名,c[10]这种写法就又定义了一个新的名字,这让人匪夷所思。

以 c 作为变量的名字,先来看括号内部(绿色粗体):
char * (* c[10]) (int **p);
[ ] 的优先级高于 *,编译器先解析c[10],c 首先是一个数组,它前面的*表明每个数组元素都是一个指针,只是还不知道指向什么类型的数据。整体上来看,(* c[10])说明 c 是一个指针数组,只是指针指向的数据类型尚未确定。

跳出括号,根据优先级规则(() 的优先级高于 *)应该先看右边(红色粗体):
char * (* c[10]) (int **p);
( )说明是一个函数,int **p是函数参数。

再看左边(橘黄色粗体):
char * (* c[10]) (int **p);
char *是函数的返回值类型。

从整体上看,我们可以将定义分成两部分:
char * (* c[10]) (int **p);
绿色粗体表明 c 是一个指针数组,红色粗体表明指针指向的数据类型,合起来就是:c 是一个拥有 10 个元素的指针数组,每个指针指向一个原型为char *func(int **p);的函数。

5) int (*(*(*pfunc)(int *))[5])(int *);

从 pfunc 开始理解,先看括号内部(绿色粗体):
int (*(*(*pfunc)(int *))[5])(int *);
pfunc 是一个指针。

跳出括号,看它的两边(红色粗体):
int (*(*(*pfunc)(int *))[5])(int *);
根据优先级规则应该先看右边的(int *),它表明这是一个函数,int *是参数列表。再看左边的*,它表明函数的返回值是一个指针,只是指针指向的数据类型尚未确定。

将上面的两部分合成一个整体,如下面的蓝色粗体所示,它表明 pfunc 是一个指向函数的指针,现在函数的参数列表确定了,也知道返回值是一个指针了(只是不知道它指向什么类型的数据)。
int (* (*(*pfunc)(int *)) [5])(int *);
蓝色粗体以外的部分,就用来说明函数返回什么类型的指针。

我们接着分析,再向外跳一层括号(红色粗体):
int (* (*(*pfunc)(int *)) [5])(int *);
[ ] 的优先级高于 *,先看右边,[5] 表示这是一个数组,再看左边,* 表示数组的每个元素都是指针。也就是说,* [5] 是一个指针数组,函数返回的指针就指向这样一个数组。

那么,指针数组中的指针又指向什么类型的数据呢?再向外跳一层括号(橘黄色粗体):
int (* (*(*pfunc)(int *)) [5]) (int *);
先看橘黄色部分的右边,它是一个函数,再看左边,它是函数的返回值类型。也就是说,指针数组中的指针指向原型为int func(int *);的函数。

将上面的三部分合起来就是:pfunc 是一个函数指针(蓝色部分),该函数的返回值是一个指针,它指向一个指针数组(红色部分),指针数组中的指针指向原型为int func(int *);的函数(橘黄色部分)。

意见反馈

微信二维码 微信扫码关注 抖音二维码 抖音扫码关注