C总结语言知识体系
目录标题写在这里
- C总结语言知识体系
- 序言:C历史、特征、语言概述标准)
-
- 为什么选择C语言进行嵌入式开发?(面试题!
- 为什么内核开发选择C语言?
- 3、C语言缺点:
-
- a)面向结构:
- b)面向对象的特征
- c)面向结构与面向对象的区别:
- 举例:
- d)面向对象方法的思想:
- e)结构化程序具有以下特点:
- 4、C面向对象的语言编程?
- 5、C语言发展方向
- 6.精通C语言,掌握C语言
- 7、总结:
- 一、C语言基础知识点
-
- 1.机器代码(运行效率,访问硬件能力)
- 2、C语言之父:丹尼斯-里奇
- 3、C语言语法标准:K&RC、c89、c99、c11(微软)
- 4、C语言语法版:GNUC ASCI C (GUNC = ASCI C 扩展)
- 5、gcc:100?9 部分c99
- 二、数据类型(基本数据类型、输入输出)
-
- 为什么要有数据类型:
- 2.数据类型分类:
- 3.定义变量(注意事项) 编码规范)
-
- a)可读性:形容词_名词
- b)循环变量:int i;int k;
- c)变量的类型决定了什么?
- d) 内存特征:
- e) 内存如何编址?
- f) 如何衡量这个内存的大小?
- g) 为什么计算机用二进制存储而不是十进制?
- 需要掌握基本数据类型的知识点:
-
- a)各种数据类型的字节长度(变量占用内存的大小)int num = 5;
- b)数值溢出的危害;
- c)解决数值溢出的对策;
- d)保存地址;
- e)数组的长度
- f)字符串长度:int strlen(char *src);不统计‘\0’
- 二进制:
- 十进制转二进制;
- 二进制转十进制;
- 八进制:
- 十进制转八进制;
- 十六进制:
- 十进制转十六进制;
- 笔试题:
- g)各种数据类型的值范围 (计算机以补码的形式保存数据, 为了解决 0, -0问题)
- 5、变量及常量:
-
- 1.变量的三个特点
- 2.作用域:可见范围:
- 3、生命周期
- 4、存储区域
- 5.堆叠和堆叠的使用原则
- 6、语言类型转换
- 类型转换的缺点
- 隐式类型转换发生在什么情况下?
- 隐式类型转换规则
- 7、格式输出:
-
- a)printf函数的使用
- b)printf函数格式控制
- 标志:
- 示例:
- 示例:
- 转义字符:
- c)printf函数缓冲区:
- d)printf函数使用技巧:
- e)使用其他输出函数:
- 8、格式输入:
-
- a)scanf函数的使用 :
- b)scanf函数注意点:
- c)scanf留 下来的垃圾:
- d)其它输入函数:
- 三、基本语句(条件语句、循环语句、多路分支、goto语句)
-
- 1.条件句:
-
- a) if语句:
- 二、循环语句:
- 3、多路分支:
-
- a)使用规则:
- b)流程图:
- c)注意事项:
- d) switch VS if:
- e) break VS continue:
- 四、数组(-维数组、二维数组、多维数组)
-
- 1.数据认知:
- 如何使用数组:
- 三、一维数组:
- 四、二维数组:
- 五、三维数组:
- 五、指针(一) 多维指针、数组指针、函数指针、函数指针组
-
- 1.指针的作用:
-
- a)谈谈你对指针的理解 什么是指针? )
- b)为什么地址分为不同类型? (不同类型的指针变化 保存不同步长的地址)
- c)指针变量及类型:
- d) 指针占用内存空间:
- e)变量指针和指针变量:
- 2、野指针
-
- a)什么是野指针?
- b)野生指针产生的问题
- c)野生指针注意事项:
- d)如何避免野指针?
- 3.内存空间分配:
- 4、malloc、 free、 calloc、 reallc:
-
- a) malloc:
- b) free:
- c) calloc:
- d) realloc:
- 六、内存管理(内存泄漏)
-
- 1.硬件(静态存储) VS内存(动态存储)差异
- 为什么所有编程都关注内存管理?
- 3.内存管理在编程过程中主要做什么?
- 4.内存错误类别:
- 5.内存泄漏的原因:
- 6.如何防止内存泄漏?
- 7、C语言防止内存泄漏的方法:
- 8.如何检查内存泄漏?
- 七、函数(字符串处理函数、时间函数、随机函数函数) )
-
- 1、什么是函数?
- 2.为什么要使用函数?
- 3、函数语法:
-
- a) 函数三要素:
- b)函数使用形式:
- c)函数调用过程:
- d)传值(传实参变量名) VS传输地址(传输参变量地址)
- e)传输参数、传输参数(大多数函数)
- f)如何返回多个函数??
- g)函数返回值:
- h)如何返回多个值?
- 4、ANSI C函数:
-
- a) ANSI C函数原型:
- b) ANSI的解决方案:
- c)错误和警告的区别:
- 5、函数原型
-
- a)优点:
- b)是否必须使用函数原型?
- c)为什么使用函数原型?
- 5、字符串函数的使用:
-
- a) strlen:
- b) strcpy:
- c) strncpy:
- d) strcat:
- e) strncat:
- h) strcmp:
- i) strncmp:
- j) memset:
- 八编码规范(华为编码规范)
- 低耦合高内聚! ! !
- 九、预处理(宏定义、宏函数、条件编译)
-
- 1、预处理主要任务:
- 2、头文件展开: #include<> VS #include"
- 3、宏替换:
- 4、内置宏介绍:
- 十、关键字(static、 extern、 register、 const、 typedef、 volatile、 inline)
-
- 1、register:
-
- a)语法作用:
- b)寄存器变量:
- c) CPU:
- d)使用场景:
- e)使用注意事项:
- 2、volatile:
-
- a)语法作用:
- b)使用场景:
- c)原理:
- d) volatile变量自身具有下列特性:
- e)当且仅当满足以下所有条件时,才应该使用volatile变量:
- 3、static:
-
- a)语法作用:
- b)使用场景:
- c)注意事项:
- d)静态变量和非静态变量的区别:
- 4、extern:
- 5、const:
-
- a)语法使用:
- b)什么是只读变量:
- c)使用场景: const:
- 6、inline:
- 十一、位操作(位运算)
-
- 1、位操作基础:
- 2、位操作运算常用小技巧
-
- a)、判断奇偶:
- b)、交换两数:
- 3.变换符号:
- 4.求绝对值:
- 3、位操作用法:
- 十二、复合数据类型(struct. union、 enum)
-
- 1、struc结构体:
-
- a)什么是结构体:
- b)结构体的作用:
- c) 定义结构体类型:
- d)结构体内能否保存函数?
- e)结构体类型定义变量:
- f)初始化(野指针的问题,直接给数组名赋值(指针常量))
- g)如何访问结构体变量的成员?
- 2、union共用体:
-
- a)使用:
- b)作用:
- c)注意:
- d) struct VS union大小
- e) CPU属性:
- d)笔试题:
- 3、enum枚举:
-
- a)作用:定义一系列的整数宏
- b)如何定义枚举变量:
- c)枚举能不能做形参?
- enum VS #define
- 十三、文件操作(打开、关闭、读写、属性设置)
-
- 1、FILE指针:
- 2、什么是数据流?
- 3、什么是缓冲区?
- 4、什么是文件
- 5、文件类型
- 6、文件存取方式
- 7、fopen () 函数
-
- a)作用:
- b)格式:
- c)模式字符串:
- 8、fprintf ()、fputs () 和fputc () 函数
-
- a)作用:
- b)格式:
- c)返回值:
- d)用法:
- 9、fclose () 函数
-
- a)作用:
- b)格式:
- 10、 fscanf ()、fgets ()和fgetc ()函数
-
- a)作用:
- b)格式:
- c)用法:
- 11、fwrite ()和frear () 函数
-
- a)用法:
- 12、 size_ tfwrite ()和size_ tfread () 函数
序言:C语言的概述历史、特点、标准)
1、嵌入式开发为什么选择C语言?(面试题!)
嵌入式开发中操作系统是核心,需要移植,并在上层和底层做开发,而操作系统的核心是内核,所有内核的开发都采用C语言,所以。。。(嵌入式开发 - 操作系统 - 内核)
2、为什么内核开发选择C语言?
a\ 能够直接访问硬件 ( C (硬件复杂操作) VS 汇编(效率 > C)(硬件初始化) )(对地址直接做操作,指针) b\ 运行效率(运行时语言) 运行时语言 VS 解释性语言(C VS java \ C(面向结构) VS C++ (面向对象) ) c\ 移植性
3、C语言的缺点:
a)面向结构:
代码的复用性差、代码的维护性差、代码的扩展性差
b)面向对象的特点
代码复用性:指的是可以直接调用; 代码扩展性:增加新功能时,不修改原来的代码; 代码维护性:即可修改性,让软件能随着用户需求的变更而容易改变。
c)面向结构和面向对象的区别:
面向过程(面向结构)就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。 (按照步骤来实现) 面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。(按照功能来实现)
举例:
五子棋,面向过程的设计思路就是首先分析问题的步骤:1、开始游戏,2、黑子先走,3、绘制画面,4、判断输赢,5、轮到白子,6、绘制画面,7、判断输赢,8、返回步骤2,9、输出最后结果。把上面每个步骤用分别的函数来实现,问题就解决了。而面向对象的设计则是从另外的思路来解决问题。整个五子棋可以分为 1、黑白双方,这两方的行为是一模一样的,2、棋盘系统,负责绘制画面,3、规则系统,负责判定诸如犯规、输赢等。第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,棋盘对象接收到了棋子的i变化就要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。 可以明显地看出,面向对象是以功能来划分问题,而不是步骤。同样是绘制棋局,这样的行为在面向过程的设计中分散在了总多步骤中,很可能出现不同的绘制版本,因为通常设计人员会考虑到实际情况进行各种各样的简化。而面向对象的设计中,绘图只可能在棋盘对象中出现,从而保证了绘图的统一。功能上的统一保证了面向对象设计的可扩展性。比如我要加入悔棋的功能,如果要改动面向过程的设计,那么从输入到判断到显示这一连串的步骤都要改动,甚至步骤之间的循序都要进行大规模调整。如果是面向对象的话,只用改动棋盘对象就行了,棋盘系统保存了黑白双方的棋谱,简单回溯就可以了,而显示和规则判断则不用顾及,同时整个对对象功能的调用顺序都没有变化,改动只是局部的。 再比如我要把这个五子棋游戏改为围棋游戏,如果你是面向过程设计,那么五子棋的规则就分布在了你的程序的每一个角落,要改动还不如重写。但是如果你当初就是面向对象的设计,那么你只用改动规则对象就可以了,五子棋和围棋的区别不就是规则吗?(当然棋盘大小好像也不一样,但是你会觉得这是一个难题吗?直接在棋盘对象中进行一番小改动就可以了。)而下棋的大致步骤从面向对象的角度来看没有任何变化。
d)面向对象方法的思想:
整个系统被分解成对象的集合,而不是功能的集合,每个对象有自己的职责,对象之间相互协作来完成用户交给的任务。 (1)同结构化相比,它不是层次结构,在结构化中,上一层依赖下一层,下一层又依赖下下一层,只要底层改变,上层也要跟着改变,它没有很好的隔离变化.而面向对象刚很好的封装了变化,外界只需使用对象的接口,而不用管这个接口是如何实现的 (2)一个重要区别是:这里的箭头是请求,而不是数据流.在结构化方法的DFD数据流程图中,箭头代表的是数据流,也就是说一个模块的输出变成另一个模块的输入,而这里,指的是对象A请求对象B完成某项任务,也就是A调用B的方法。 从结构化到面向对象的两个误区,也就是两个极端: 1.过大的类.一个类中包含了几十个函数,这种大杂烩严重违背面向对象的单一职责原则。也就是说,一个对象要干的事应该和它的名字一致,它要干哪些事,从它的名字就应该能看出来. 2.类中只有函数,没有属性.这是陷入了功能分解的误区,只是简单的把函数组装到一起.基本没有封装。 对象的一个重要特性是:对象是有状态的(也就是属性),对象的行为与它的所处的状态密切相关。
e)结构化程序具有以下几个特征:
自顶向下,逐步细化,模块化设计,结构化编码
4、C语言实现面向对象编程?
5、C语言的开发方向
操作系统(上层(库)、底层(BSP、 驱动)、实现(内核) )、硬件、中间件(sdk)
6、精通C语言、掌握C语言
需要: 代码的累积量 阅读书籍(整理笔记时) 项目经验
7、总结:
一、C语言基础知识点
1、机器码(运行效率、访问硬件能力)
2、C语言之父:丹尼斯-里奇
3、C语言语法标准:K&RC、c89、c99、c11(微软)
4、C语言语法版本:GNUC+ASCI C (GUNC = ASCI C +扩展)
5、gcc:100%c89 + 部分c99
二、数据类型(基本数据类型、输入输出)
1、为什么要有数据类型:
计算机中内存也是有限的,为了提高内存使用效率,不浪费空间,自然是需要设计出数据类型来划定多大数据占多大内存空间,就有了数据类型。
2、数据类型分类:
基本数据类型(内置:编译器自带的类型) : int 4个字节(指针 8个字节:int *…) short 2个字节 long 8个字节 long long 8个字节 char 1个字节 float 4个字节 double 8个字节 (字节长度笔试题) 复合数据类型(多个内置类型的组成的新类型) :数组、struct, union、 enum;void类型: void * (万能指针) :多态
3、定义变量(注意事项 编码规范)
a)变量的可读性:形容词_名词
int sum_ result (错误示范 int a;int b; int c; int d)(int qiuhe)
b)循环变量:int i;int k;
c)变量的类型决定了什么?
(1)占用内存空间的大小(内存是计算机内的存储部件,代码和数据都存在其中。) (2)数据的存储形式 (在Inter 的CPU上面,这个整型数他的存储是低位字节在前高位字节在后,相当于脚先进去;实型数的存储稍微复杂一点,我们通常说是定点数,但是在计算机中保存是按照浮点数来保存的) (3)合法的取值范围 (4)可参与的运算种类
d) 内存的特点:
所有指令和数据都保存在内存里,速度快,可随访问,但掉电即失去(掉电就没了)。
e) 内存如何编址呢?
你声明一个变量,他可能就分配了一个内存空间,那么他得有个地址。 地址是按照字节编号的,内存中每个字节都有唯一的编号。(就像我们的宿舍不是按照床位编号的,是按房间编号的,没有编制到位而是字节,每一个字节有8位,每个字节都有一个唯一的编号,这个编号就是他的地址,这个地址是一个什么样的数?他是一个整数是无符号的) 地址是一个16进制无符号整数,字长与主机相同,32位计算机的内存地址编码是32位,从0x00000000,到0xFFFFFFFF。(为什么用16进制来表示,因为16进制是2进制压缩的一个版本,用16进制来表示2进制很简单,比较短;字长与主机相同,所以32位计算机呢,他的内存地址编码就是32位;16进制一个符号代表4位,总共8个符号,4x8=32) 按变量声明的类型分配内存空间,大小类似于博士生、硕士生、本科生分配寝室。(按变量声明的类型来分配内存大小,几个字节几个字节。)
f) 如何衡量这个内存的大小呢?
计算机存储数据的最基本单元,衡量物理存储器容量的最小单位----------位,也称为比特。那一个位有多大呢?要么是0,要么是1;那我们知道了为什么计算机只能识别0和1,因为它存在里边的就是0和1,他存的不是别的就是0和1,就是二进制。
g) 为什么计算机用二进制存储,不用十进制?
4、基本数据类型需掌握的知识点:
a)各种数据类型的字节长度(变量占用内存的大小)int num = 5;
注:计算机表示内存大小的单位: bit位、 字节、半字、字.双字、kB、MB、G. T 8bit位= 1个字节、 16bit = 2个字节=半字 32bit = 4个字节=字 1024kB = 1MB
b)数值溢出的危害;
(1)编译器对它熟视无睹;(某些编译器并不给你提示,所以发生的时候你没有预防) 整形溢出导致死循环
c)解决数值溢出的对策;
(1)了解处理问题的规模,选择取值范围更大的变量类型。 比如明明我可以用单精度实型我用双精度实型?那这样是不是影响他的运算效率呢?确实,你使用更大范围的、精度更高的这样类型的话,他的运行效率会受到一点点影响。 (2)不要对变量所占的内存空间字节数想当然。 (3)用sizeof获得变量或者数据类型的长度。
d)保存地址;
操作系统中地址的长度是固定长度,是有操作系统位数决定的,64位系统是64位系统是8个字节,32位系统是4个字节
e)数组的长度
数组的长度 * 元素类型的长度
f)字符串长度:int strlen(char *src);不统计‘\0’
二进制:
二进制逢二进一,所有的数组是0、1组成
十进制转二进制;
除二反序取余法:将十进制数每次除以2 取出余数 按照结果倒叙依次获取结果
二进制转十进制;
权值法:将二进制数各个位数从0位开始乘以2的N幂 将各个位数的结果相加
八进制:
八进制逢八进一,所有的数组是0到7组成
十进制转八进制;
除八反序取余法:将十进制数每次除以8 取出余数 按照结果倒叙依次获取结果
十六进制:
十六进制逢十六进一,所有的数组是0到9和A到F组成 字母不区分大小写
十进制转十六进制;
除十六反序取余法:将十进制数每次除以16 取出余数 按照结果倒叙依次获取结果
笔试题:
输出结果: 8 100 11 11 正数: 原码=补码 负数: 补码=原码取反+ 1; 原码=补码取反+ 1;
g)各种数据类型的取值范围? (计算机是以补码形式保存数据, 为了解决+0, -0问题)
注:大数计算20030434043243204324 * 32432432432432432? ? ? 笔试题 1\计算机为什么提出补码存储? +0 -0 00000000=+0 10000000=-0 I 2、printf ("%d\n",~2) ; 0000 0010 1111 1101 000 0010 +1 000 0011 = -3 ‘0’ a A 0 扩展(计算机为什么用补码储存): a)为什么不以原码形式存储? 首先,原码是站在用户角度的,是原始的二进制! 求原码: 1.用户的数字分为正负数,需要有一位存储符号 2.最高位为符号位:0为正,1为负 3.左边是高位,右边是低位 由原码的计算方式可以发现源码存储会引发2个问题: 1.0有两个存储方式 我们以char型(占1字节,8位)为例(下同): +0: 0000 0000 -0: 1000 0000 不难发现+0和-0的原码是不一样的,而在计算过程中,+0和-0是没有任何区别的。 2. 正数和负数相加,结果不正确(计算机只会加) 1 - 1 =1 + (-1) 1: 0000 0001 -1: 1000 0001 1000 0010 = -2 很显然1-1=0,而用原码进行计算得出的结果却相差甚远! b)为什么不以反码形式存储? 既然原码不适合作为计算机的存储方式,人们在解决这个问题的过程中又提出了反码的概念 求反码: 求原码 符号为不变,其他位取反 注意:正数原码、反码一样! 接下来我们检验一下1 - 1: 1: 0000 0001
- 1: 1111 1110 1111 1111 ->1000 000(转换为原码,因为原码是站在用户角度的) = -0 不难可能出反码已经解决的正负数相加结果不正确的问题 然后我们再检验+0、-0: +0 0000 0000 -0 1111 1111 由此看出,反码并没有解决0有两种形式的问题 反码存储会引发1个问题: 0有两个存储方式 c)补码存储 在反码的基础上,人们有提出了补码的概念 求补码: 补码为其反码+1 注意:正数的原码、反码、补码都一样! 接下来我们对补码存储进行验证: +0: 0000 0000 -0: 原码: 1000 0000 反码: 1111 1111 补码:1 0000 0000(char占1字节八位,最高位丢弃)= 0000 0000 可以看出补码解决了+0 -0不一样的问题 +1: 0000 0001 -1: 1111 1111 1 0000 0000(最高位丢弃)=0000 0000 = 0 那么补码也解决了正负数相加结果不正确的问题! 8、typedef 关键字:给数据类型重命名 a\ 解决signed,unsigned带来的代码移植性问题 b\ 提高了代码的可读性 c\ 提高编码效率
5、变量和常量:
1、变量的三大特点
字节长度、生命周期&作用域、存储区域
2、作用域:可见范围
局部变量:在函数体里定义的变量–所在函数(出了函数不可见) 全局变量:在函数体外定义的变量-整个全局(看不见需要extern外部声明)
3、生命周期
所在内存空间的分配-释放的过程 局部变量:所在函数体执行时,分配空间,执行结束,释放空间 全局变量:所在程序执行时,分配空间,执行结束,释放空间
4、存储区域
局部变量;存储在栈空间 全局变量;存储在数据段 栈空间局部变量未初始化默认初始化 数据段全局变量未初始化默认初始化为0 gcc 4.0以上 定义:分布内存空间,只能定义一次。 声明:不分配内存空间,可声明多次。
5、堆和栈的使用原则
6、语言类型转换
int,char:相关类型 不安全的:可以将任何类型之间转换,有可能造成数据丢失 安全的:检查两个类型是否可以转换 隐式类型转换:char->short->int->long->float->double 强制类型转换:(变量名称) 变量名
类型转换缺点
隐式类型转换 可能会因为整形提升或者数据截断导致 精度的丢失,并且有时候会因为 忽略隐式类型转换导致错误发生。 显示类型转换 代码不够清晰,没有很好的将各种情况划分开,而是全部混在一起使用。
什么情况下发生隐式类型转换?
算术运算中,低类型转换为高类型 赋值表达式中,赋值符“=”右边的变量值转换为左边变量的类型 函数调用时,实参转换为形参的类型 函数返回时,函数返回值,ruturn表达式转换为返回值类型
隐式类型转换的规则
确定二元运算中的哪个操作数要转换为另一个操作数的类型时,其机制相当简单。其基本规则是,值域较小的操作数类型转换为另一个操作数类型,但在一些情况下,两个操作数都要转换类型。
为了准确地表述这些规则,需要比上述更复杂的描述,所以可以忽略一些细节,在以后需要时再考虑他们。
编译器按顺序采用如下规则,确定要使用的隐式类型转换:
如果一个操作数的类型是long double,把另一个操作数类型转换为long double类型。 否则,如果一个操作数的类型是double,就把另一个操作数类型转换为double类型。 否则,如果一个操作数的类型是float,就把另一个操作数类型转换为float类型。 否则,如果两个操作数的类型都是带符号的整数或无符号的整数,就把级别较低的操作数类型转换为另一个操作数的类型。无符号整数类型的级别从低到高为: signed char, short, int, long, long long 每个无符号整数类型的级别都与对应的带符号整数类型相同,所以 unsigned int类型的级别与int类型相同。 否则,如果带符号整数类型的操作数级别低于无符号整数类型的级别,就把带符号整数类型的操作数转换为无符号整数类型。 否则,如果带符号整数类型的值域包含了无符号整数类型所表示的值,就把无符号整数类型转换为带符号整数类型。 否则,两个操作数都转换为带符号整数类型对应的无符号整数类型。
7、格式化输出:
a)printf函数的使用
基本使用语法: a)格式:printf(“格式控制串”,输出表); b)功能:按指定格式向显示器输出数据 c)反值:正常,返回输出字节数;出错,返回EOF(-1)
b)printf函数格式控制
类型:
标志:
示例:
输出结果:
示例:
输出结果:
转义字符:
c)printf函数缓冲区:
在 printf 的实现中,在调用 write 之前先写入 IO 缓冲区,这是一个用户空间的缓冲。系统调用是软中断,频繁调用,需要频繁陷入内核态,这样的效率不是很高,而 printf 实际是向用户空间的 IO 缓冲写,在满足条件的情况下才会调用 write 系统调用,减少 IO 次数,提高效率。 printf(…)在 glibc 中默认为行缓冲,遇到以下几种情况会刷新缓冲区,输出内容: (1)缓冲区填满; (2)写入的字符中有换行符\n或回车符\r; (3)调用 fflush(…) 手动刷新缓冲区; (4)调用 scanf(…) 从输入缓冲区中读取数据时,也会将输出缓冲区内的数据刷新。 可使用setbuf(stdout,NULL)关闭行缓冲,或者setbuf(stdout,uBuff)设置新的缓冲区,uBuff 为自己指定的缓冲区。也可以使用setvbuf(stdout,NULL,_IOFBF,0);来改变标准输出为全缓冲。全缓冲与行缓冲的区别在于遇到换行符不刷新缓冲区。 printf(…) 在 VC++ 中默认关闭缓冲区,输出时会及时输到屏幕如果显示开启缓冲区,只能设置全缓冲。因为微软闭源,所以无法研究 printf(…) 的实现源码。 Linux 和 Windows 下的缓冲区管理可见:C的全缓冲、行缓冲和无缓冲。
d)printf函数使用技巧:
printf 与 wprintf 不能同时使用 在输出宽字符串时,发现将 printf 和 wprintf 同时使用时,则后使用的函数没有输出。这里建议不要同时使用 printf 和 wprintf,以免发生错误。 这里是因为输出流在被创建时,不存在流定向,一旦使用了 printf(多字节字符串) 或 wprintf(宽字符串) 后,就 被设置为对应的流定向,且无法更改。可以使用如下函数获取当前输出流的流定向。 通过 fwide 可以设置当前流定向,前提是未有任何的 I/O 操作,也就是当前流尚未被设置任何流定向。顺带吐槽一下,不知为何标准库函数 fwide 实现的如此受限。具体操作如下: 设置标准输出流定向为多字节流定向 设置标准输出流定向为宽字符流定向 既然 GNUC 存在这个问题,那该如何解决呢?这里有两种办法: (1)统一使用一种函数。 (2)使用 C 标准库函数 freopen(…) 清空流定向,可以让printf(…) 与 wprintf(…)同时使用。
e)其他输出函数的使用:
putchar,putc,puts
8、格式化输入:
a)scanf函数的使用 :
格式:scanf(“格式控制串”,地址表); 功能:按指定格式从键盘读入数据,存入地址表指定存储单元,并按回车结束 反值:正常;返回输入数据个数 地址表:变量的地址,常用取地址运算符& 输入数据时,遇到以下情况认为该数据结束: 空格、TAB、回车 宽度结束 非法输入
b)scanf函数注意要点:
在 scanf 的“输入参数”中,变量前面的取地址符&不要忘记。 scanf 中双引号内,除了“输入控制符”外什么都不要写。 “输出控制符”和“输出参数”无论在“顺序上”还是在“个数上”一定要一一对应。 “输入控制符”的类型和变量所定义的类型一定要一致。对于从键盘输入的数据的类型,数据是用户输入的,程序员是无法决定的,所以在写程序时要考虑容错处理,这个稍后再讲。 使用 scanf 之前先用 printf 提示输入。用格式串中
c)scanf留 下来的垃圾:
用getchar()清除 用格式串中空格或"%*c"来"吃掉"
d)其他输入函数:
getchar,getc,gets
三、基本语句(条件语句、循环语句、多路分支、goto语句)
1、条件语句:
a) if语句:
1)成立:非零 2)if后的;!!! 3)判断相等时,将常量写到左面 注意:“==”判断是否相等;“=”:赋值 4)规范: if后面必须匹配else 5)if条件中的运算符优先级 6)判断条件的零值比较: if(!num)VS if( 0 == num) 布尔变量: if(flag) 表示flag为真 if(!flag)表示flag为假 整型: if(value == 0) if(value != 0) 浮点类型: 假设浮点类型的名字是x if(x == 0.0) //隐含错误的比较 转化为: if((x >= -EPSINON)&& (x <= EPSINON)) 其中,EPSINON是允许误差(即精度) 指针: if(p == NULL) //强调了p是指针变量 if(p != NULL) 若写成 if(p == 0) //误以为p是整型变量 if(p) //误以为p是布尔变量
2、循环语句:
嵌入式中的死循环: for( ; ; ) while( 1 ) 注意事项: 使用选择: 已知循环次数用for,未知循环次数用while 循环语句的条件不能多加分号,与if判断语句一样!!! 循环体里不要直接修改循环变量的值;(循环变量在循环体中没有发生变化)死循环 循环方式: for循环: while循环 do…while循环
3、多路分支:
a)使用规则:
switch 语句中的 expression 是一个常量表达式,必须是一个整型或枚举类型。 在一个 switch 中可以有任意数量的 case 语句。每个 case 后跟一个要比较的值和一个冒号。 case 的 constant-expression 必须与 switch 中的变量具有相同的数据类型,且必须是一个常量或字面量。 当被测试的变量等于 case 中的常量时,case 后跟的语句将被执行,直到遇到 break 语句为止。 当遇到 break 语句时,switch 终止,控制流将跳转到 switch 语句后的下一行。 不是每一个 case 都需要包含 break。如果 case 语句不包含 break,控制流将会 继续 后续的 case,直到遇到 break 为止。 语句中的 expression 是一个常量表达式,必须是一个整型或枚举类型。 一个 switch 语句可以有一个可选的 default case,出现在 switch 的结尾。default case 可用于在上面所有 case 都不为真时执行一个任务。default case 中的 break 语句不是必需的。
b)流程图:
c)注意事项:
switch语句中表达式类型只能是整型或者字符型。 case里如果没有break,那么程序会一直向下执行。
d) switch VS if:
与if语句比,对于多条件判断时,switch的结构清晰,执行效率高,缺点是switch不可以判断区间。
e) break VS continue:
四、数组(-维数组、二维数组、多维数组)
1、数据认知:
a)静态分配空间(int a [100]:400个字节) int a [?] --> 空间利用率差(1、不够用;2、浪费空间) b)所占内存空间特点:连续的(物理连接)–> malloc分配空间是否物理连接?(malloc实验原理:链表连接所有空闲的空间,组成最终分配的空间)
2、如何使用数组:
3、一维数组:
a:数组名,指针常量,保存数组首元素的地址 &a:对数组名取地址,等于数组的地址 *(&a)= a:对一维数组的地址取值等于数组首元素的地址;整型变量的得知用整形指针变量,字符变量的地址用字符指针变量,数组的地址用数组指针变量保存 数组指针变量:变量,保存的是地址,该地址是数组的地址 int(*pa)[3];pa + 1:
4、二维数组:
二维数组的定义:不能省略行,可以省略列 aa:指针常量,保存的是首个一维数组的地址 &aa:二维数组的地址 *(&aa)= aa:对二维数组的地址取值等于首个一维数组的地址 *aa:首个一维数组的收个元素的地址 ((aa + i)+ j) aa:二维数组中首个一维数组的地址 aa + i:二维数组中第i + 1个一维数组的地址 *(aa + i):二维数组中第i + 1个一维数组的首元素的地址 *(aa + i)+j:二维数组中第i + 1个一维数组的第j + 1个元素的地址 ((aa + i)+ j):二维数组中第i + 1个一维数组的第j + 1个元素的地址的值
5、三维数组:
例:int aaa[2][2][2] = { {1, 2, 3, 4},{5, 6, 7, 8}} 第一个下标:第几个二维数组 第二个下标:第几个二维数组的第几行 第三个下标:第几个二维数组的几行第几列 三维数组的作用:首个二维数组的地址 aaa:首个二维数组的首个一维数组的地址 **aaa:首二维数组的首个一维数组的首元素的地址 **aaa:首个二维数组的首个一维数组的首元系的值 &aaa:三维数组的地址 (&aaa) = aaa;对三维数组的地址取值等于首个二维数组的地址 //((*(aaa +i) +j)+k)aaa + i:三维数组中第i+1个二维数组的地址 *(aaa + i):三维数组中第i+1个二维数组的首个一维数组的地址 (aaa +i) +j:三维数组中第i+1个二维数组的第j+1个一维数组的地址 ((aaa + i)+ j):三维数组中第i+1个二维数组的第j+1个一维数组的首元素的地址 ((aaa + i) + j)+k:三维数组中第i+1个二维数组的第j+1个一维数组的第k+1个元素的地址 (((aaa +i) + j)+ k):三维数组中第i+1个二维数组的第j+1个一维数组的第k+1个元素的值
五、指针(一 维指针、多维指针、数组指针、函数指针、函数指针组)
1、指针的作用:
a)谈谈你对指针的理解? (指针是什么? )
语法: 指针是一种数据类型,它可以定义变量,变量保存的值是地址,由于地址是固定长度,所以指针变量的长度是固定的;不同地址的步长不一样,需要不同指针类型的变量来保存 作用: 由于指针变量可以保存地址,所以可以直接操作地址,也就是可以直接操作硬件的寄存器地址,从而实现直接访问硬件 支持的运算符: :间接运算符 &:取地址运算符(对应的内存空间,指向的内存空间) (“&”和“”都是右结合的。假设有变量 x = 10,则*&x 的含义是,先获取变量 x 的地址,再获取地址中的内容。因为“&”和“*”互为逆运算,所以 x = *&x。) p++:p对应的内存空间 (*p)++:p指向的内存空间 多级指针:保存上一级指针变量的地址(远指针(巨指针),近指针) 注:什么时候使用多级指针? 应用场景:函数传参 指针之间的运算: 赋值:指针之间的赋值:相同类型指针之间的赋值 int *p;//整型指针变量:p是一个变量。保存的是地址,该地址是整型类型; char *p2:;//字符指针变量:p2是一个变量,保存的是地址,该地址是字符类型的地址 关系运算
b)地址为什么分为不同类型? (不同类型的指针变 量保存不同步长的地址)
地址属性:步长(什么是步长:增加1操作所移动的字节) 地址 + 操作数 = 地址 eg:0x00000001 + 1 =
c)指针变量和指针的类型:
指针变量就是一个变量,它存储的内容是一个指针。(数据类型 *指针名)
在我们定义一个变量的时候,要确定它的类型。(int 变量的指针需要用 int 类型的指针存储,float 变量的指针需要用 float 类型的指针存储。就像你只能用酒店 A 的房卡存储酒店 A 中房间号的信息一样。)
d) 指针占用内存空间大小:
与指针指向的内容和内容的大小无关。
在不同的操作系统及编译环境中,指针类型占用的字节数是不同的:
编译生成16位的代码时,指针占2个字节
编译生成32位的代码时,指针占4个字节
编译生成64位的代码时,指针占8个字节
32位系统指针占4位,64位系统指针就是占8位了
对于某一个具体的环境,可以用下面的语句精确的知道指针类型占用的字节数:
printf("%d\n", sizeof(int *));
另外,int型占用4字节(无论32位系统还是64位系统)
e)变量的指针与指针变量:
变量的指针就是变量的存储地址 指针变量就是存储指针的变量数
2、野指针
a)什么是野指针?
定义未初始化指针 释放结束之后指针 越界访问的指针 注: char *p 野指针:指针变量里内存的地址对应空间无访问权限(指针变量所指向空间无访问权限)
b)野指针产生的问题?
内存泄漏—运行时错误—内存错误(段错误)
c)野指针的注意事项:
1、指针指向常量存储区对象 2、资源泄露 3、内存越界 4、返回值是指针 5、指针做形参
d)如何避免野指针?
养成良好的编码习惯: 1)定义指针变量时必须初始化: 当指针变量作为指向工具时,定义时初始化为NULL; 当向指针变量指向的空间赋值时,需要给动态申请空间 2)使用时: 检查内存空间是否分配成功 初始化内存空间 防止越界访问 3)使用结束时: 必须要释放空间 释放之后一定要将指针再次初始化为NULL NULL:#define NULL (void *)0 NULL代表的是0地址(不能访问,不能储存数据段) char *p = NULL;//EOF 注意:野指针不能杜绝,只能避免!!!
3、内存空间分配:
1)静态分配:开销小,但是空间利用率高 2)动态分配:开销大,提高空间的利用率 动态分配(字节为单位):在堆空间分配 malloc:void *malloc(size_t size); size:sizeof(类型)*数量 char *p = (char *)malloc(sizeof(char) *100); malloc VS calloc:callpc初始为0; realloc:void *realloc(void *ptr,size_t size); 当ptr置为NULL时:相当于malloc;malloc(100) == realloc(NULL,100); 当ptr不为NULL时: size大于原来分配的空间大小,数据不会丢失,但是存在越界访问的可能性; size小于原来分配到空间大小,数据不会丢失,但是指针的指向会发生改变,因为realloc的机制是重新分配并不是追加,所以realloc开销较大(拷贝) sizeof == 0相当于free();realloc(p,0)== free(p); 注意事项:检查是否分配成功;返回值类型转换(相同指针类型赋值);大小必须通过sizeof(类型)*数量(保证足够的空间) 动态分配内存为什么开销比较大: 多分配了内存用来保存使用内存信息
4、malloc、 free、 calloc、 reallc:
a) malloc:
void * malloc(size_t size); 这个函数可以再堆区上开辟内存连续可用的空间: 如果开辟成功,则返回一个指向开辟好空间的指针 如果开辟失败,则返回NULL指针,因此malloc的返回值一定要做检查 由于返回值的类型是void *,所以malloc函数并不知道开辟空间的类型 具体在使用的时候由使用者自己来决定 如果参数size为0,malloc的行为的标准是未定义的,取决于编译器
b) free:
void free(void * ptr); 如果参数ptr指向的空间不是动态开辟的,那么free函数的行为是未定义的 如果参数ptr是NULL指针,则函数什么是都不用做
c) calloc:
void * callloc(size_t num,size_t size); calloc与malloc的开辟方式唯一的不同点是在开辟内存的同时初始化
d) realloc:
void * realloc(void * ptr,size_t size); realloc函数可以对动态开辟内存大小的调整 ptr是要调整的内存地址size调整之后新大小返回值为调整之后的内存其实位置 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间: 1)原有空间之后有足够大的空间 2)原有空间之后没有足够大的空间
六、内存管理(内存泄漏)
1、硬件(静态存储) VS内存(动态存储)区别?
2、为什么所有的编程都关注内存管理?
内存属于稀缺资源
3、编程过程中,内存管理主要做什么?
防止内存泄漏
4、内存错误的类别:
a)内存泄漏 b)错误分配(包括大量增加free()释放的内存和为初始化的引用) c)悬空指针 d)数组边界违规
5、内存泄漏的原因:
a)内存分配未成功,却使用它 b)内存分配虽然成功,但尚未初始化就引用它 c)内存分配成功并且已经初始化,但操作越过了内存的边界 d)忘记释放内存,造成内存泄漏
6、如何防止内存泄漏?
用户自己管理: 缺点:对用户的要求比较高(良好的编码习惯,经验性) 优点:开销小,实时 系统管理: 缺点:开销大,实时性差,用户无法干预(GC:垃圾回收机制) 优点:能够有效的防止内存泄漏
7、C语言防止内存泄漏的方法:
a)养成良好的编码习惯 b)内存区域的划分(Linux虚拟地址空间) c)动态分配内存的方式选择
8、如何检查内存泄漏?
内存分析诊断工具valgrind
七、函数(函数的声明、定义、调用、库函数的使用(字符串处理函数、时间函数、随机数函数) )
1、什么是函数?
函数是完成特定任务的独立程序代码单元,语法规则定义了函数的结构和使用方式
2、为什么要是用函数?
使用函数可以省去编写重复代码的苦差,函数可以让程序更加模块化,从而提高了程序代码的可读性和维护性
3、函数语法:
a) 函数三要素:
函数名:命名体现注释性、提高代码的可读性:动词——名词(4) 函数形参:传什么类型就要用什么类型的变量来接(例:a == 元素指针/aa == int(*a)[]/aa == int(*aa)[][]) 函数返回值:return 0;(结束当前程序)/ exit(1)(结束整个程序)
b)函数的使用形式:
函数的声明(在调