C语言从入门到精通方法
每天进步一点点,日积月累。
纸上得来终觉浅,绝知此事要躬行。
Talk is cheap,show me the code
C 语言概述
1. 为什么学习C语言
1.1 C语言的起源和发展
-
第一代语言:机器语言(0000、1111、1101)
-
第二代语言:汇编语言(ADD、AX、BX)
-
第三代高级语言
-
结构化语言:使得它的数据和操作是分离的
- Fortran(用于科学计算)
- Basic(->VB)
- C语言
- Pascal(用于教学)
-
面向对象语言
- Algo simula67
- Ada smallTalk
- C++
- Java(->C#)
速度:C>C++>Java
-
1.2 C语言的特点
优点:代码量小、速度快、功能强大
windows C内核 C++外包
Unix C
linux C
缺点:危险性高、开发周期长、可移植性不强
1. 3 C语言的应用领域
- 系统软件开发
1. 操作系统:Windows、Unix、Linux
2. 驱动程序:主板、显卡、摄像头驱动
3. 数据库:DB2、Oracle、Sql server - 应用软件开发
1. 办公软件
2. 图形图像软件
3. 嵌入式软件
4. 游戏开发
1.4 模块化程序设计思想
- 在设计较复杂的程序时,一般采用自顶向下的方法,将问题划分为几个部分,各个部分再进行细化,直到分解为较好解决问题为止。
- 模块化设计,简单地说就是程序的编写不是一开始就逐条录入计算机语句和指令,而是首先用主程序、子程序、子过程等框架把软件的主要结构和流程描述出来,并定义和调试好各个框架之间的输入、输出链接关系逐步求精的结果,是得到一系列以功能块为单位的算法描述。以功能块为单位进行程序设计,实现其求解算法的方法称为模块化。
- 模块化的目的是为了降低程序复杂度,使程序设计、调试和维护等操作简单化。
- 利用函数,不仅可以实现程序的模块化,使得程序设计更加简单和直观,从而提高了程序的易读性和可维护性,而且还可以把程序中经常用到的一些计算或操作编写成通用函数,以供随时调用
举例:
2. C语言编程预备知识
2.1 CPU、内存条、硬盘、显卡、主板、显示器之间的关系
- 某双击硬盘上存放的文件(视频,音频),单击后操作系统将硬盘上数据调入内存条中,CPU去处理内存条中数据,图像的话通过显卡在显示器显示。
2.2 程序如何运行起来
-
文本编译软件上写完程序后,点击编译(Compiler)、链接(Linker)之后Visual C++生成 .exe可执行文件,再点击运行(Built)之后,Visual C++请求OS去调用CPU执行 .exe文件。
-
编译:由编译程序对用户源程序进行编译,形成若干个目标模块(Object Module)。
-
链接:由链接程序将编译后形成的一组目标模块以及它们所需要的库函数链接在一起,形成一个完整的装入模块(Load Module)。
-
装入:由装入程序将装入模块装入内存。
2.3 编译预处理
-
定义
#开头的是编译预处理指令,它们不是C语言成分,但是C语言离不开它们 -
宏定义
不带参数的宏定义
#define 宏名 替换文本 #define PI 3.14159 在C语言编译器开始编译前,编译预处理程序会把程序中的名字替换成值
带参数的宏定义
#define 宏名 (参数表) 字符串 #define RADTODEG ((x)*57.29)
2.4 标识符
命名规则:
只能由字母与数字、下划线组成
第一个字符必须是字母或下划线
区分字母大小写
不能是C语言关键字
分类:
关键字
预定义标识符
用户标识符
2.5 什么是数据类型
C语言要求在定义所有的变量时都要指定变量的类型,常量也是区分类型的。
- 所谓类型,就是对数据分配存储单元的安排,包括存储单元的长度(占多少字节)以及数据的存储形式。不同的类型分配不同的长度和存储形式。
以下是谭浩强的c程序设计书籍,更为准确。
2.6 什么是变量
变量的本质:是内存中一段存储空间,不同类型的变量其存储单元大小不同
- 定义:程序运行期间,变量的值可以改变
- 存储单元里存放的是该变量的值
- 变量要有变量名,在使用前必须先定义
变量必须先定义后使用。变量名实际上是以一个名字代表的一个存储地址。在对程序编译链接时由编译系统给每个变量名分配对应的内存地址。从变量中取值,实际上是通过变量名找到相应的内存地址,从该存储单元中读取数据。
变量的数据类型:
C语言中字符串只有常量没有变量,而单个字符有变量形式和常量形式即字符变量和字符常量。
- java语言中直接就有string这个数据类型,因而可以直接使用字符串变量。而C语言中没有字符串数据类型,所以使用字符串的时候,只能当做字符串常量来使用。难免会不是很方便,那么现在就总结一下C语言中字符串的使用!
- C语言字符串常量的使用
字符型数据与整型数据之间可以通用,一个字符能用字符的形式输出,也能用整数的形式输出(字符的本质上是代表一个十进制整数)
变量的运算:
- 字符数据进行算术运算,相当于对他们的ASCII码进行运算
/*
字母转换来理解字符变量的运算过程
以下为小写字符转换为大写字符
*/
#include <stdio.h>
int main(void)
{
char ch;
scanf("%c", &ch);
//方式一
printf("%c\n", ch-32);//由于字符本质就是使用ASCII码的十进制整数进行存储,所以可以直接运算
//方式二
printf("%c\n", ch-'\x20');//十六进制的'\x20'表示十进制32所代表的字符
//方式三
printf("%c\n", ch-' ');//空格字符' '表示十进制的32
return 0;
}
总结:
-
C语言并无char类型,就是用Int表示char的!char占一个字节,在C语言所有类型中最小。
-
在C语言中,实际上字符型数据在内存中是以二进制形式存放的,并不是真正的把一个字符存进内存里。在对字符型数据进行相加减运算的时候,系统会首先会将char型数据以隐形的方式转化成int型数据再进行相加减运算的。因此,由字符型数据在内存的存储方式来看,字符型数据是可以进行数值运算的,对字符型数据做数字运算实际上就是对字符本身对应的ASCII码进行相应的数值运算。
-
“ 整型”和“字符型”可以互相转换:
(char)整型变量=字符型变量 (int)字符型变量=整型变量
2.7 变量为什么必须初始化(赋值)
软件运行期间,软件所占的内存空间不再分配给其他软件,当软件运行完毕后,OS将回收内存空间(OS并不清空该内存空间遗留下的数据)
2.8 如何定义变量
数据类型 变量名=要赋的值
(int price = 10;)
2.9 进制
1. 什么叫进制
十进制就是逢十进一:0 1 2 3 4 5 6 7 8 9 10 11
二进制就是逢二进一:0 1 10 11 100 101 110 111
十六进制就是逢十六进一:0 1 2 3 4 5 6 7 8 9 A B C D E F
2. 进制转换的预备知识
3. 把r进制转换为十进制
4. 把十进制转换成r进制
-
把十进制转换为二进制
-
把十进制转换为八进制
-
把十进制转换为十六进制
十进制转r进制方法:除r取余,直至商为0,余数倒序排列 -
二进制与十六进制转换
一个十六进制位必须用4个二进制位表示一个八进制为必须用是三个二进制位表示
-
不同进制所代表的的数值之间的关系
- 十进制的3981转换为十六进制是F8D
- 十进制的3981和十六进制的F8D所代表的本质上都是同一个数
- C语言规定八进制前加:0
- C语言规定十六进制前加:0X
- 二进制的101和十进制的5,本质上是一样的,只是外部形式不同。
常用计数制对照表
同一个值不同的进制表示形式不同
2.10 什么是常量
在程序运行中,其值不能被改变的量。
2.11 常量在C语言中如何表示
-
在字符集中,有一类字符具有这样的特性:当从键盘上输入这个字符时,显示器上 就可以显示这个字符,即输入什么就显示什么。这类字符称为可显示字符,如a、b、c、$、+和空格符等都是可显示字符。
-
另一类字符却没有这种特性。它们或者在键盘上找不到对应的一个键(当然可以用特殊方式输入),或者当按键以后不能显示键面上的字符。其实,这类字符是为控制作用而设计的,故称为控制字符。 在C语言中,构成字符常量的控制字符必须用转义字符表示。
举例:转义字符必须也要加’\ ’
转义字符是C语言中表示字符的一种特殊形式,其含义是将反斜杠后面的字符转换成另外的意义
使用单撇号括起一个字符的形式即字符常量需要注意以下几点:
(1)字符常量只能用单撇号括起来,不能使用单引号或其他括号。
(2)字符常量中只能包括一个字符,不能是字符串。
(3)字符常量是区分大小写的。
(4)单撇号只是界限符,不属于字符常量中旳一部分,字符常量只能是一个字符,不包括单撇号。
(5)单撇号里面可以是数字、字母等C语言字符集中除’和\以外所有可现实的单个字符,但是数字被定义为字符之后则不能参与数值运算。'\0':表示转义字符,结果为0 '\ddd':三位8进制的数字,但是每个数字都不能超过8
通常使用转义字符表示ASCII码字符集中不可打印的控制字符和特定功能的字符,下表是常用的转义字符及其含义。
转义字符 意义 ASCII码值(十进制) \a 响铃(BEL) 007 \b 退格(BS) ,将当前位置移到前一列 008 \f 换页(FF),将当前位置移到下页开头 012 \n 换行(LF) ,将当前位置移到下一行开头 010 \r 回车(CR) ,将当前位置移到本行开头 013 \t 水平制表(HT) (跳到下一个TAB位置) 009 \v 垂直制表(VT) 011 \|代表一个反斜线字符’ \ ’ 092 \’ 代表一个单引号(撇号)字符 039 \" 代表一个双引号字符 034 \0 空字符(NULL) 000 \ddd 1到3位八进制数所代表的任意字符 3位八进制 \xhh 1到2位十六进制所代表的任意字符 2位十六进制 由上可知,使用八进制转义字符和十六进制转义字符,不仅可以表示控制字符,而且也可以表示可显示字符。但由于不同的计算机系统上采用的字符集可能不同,因此,为了能使所编写的程序可以方便地移植到其他的计算机系统上运行,程序中应少用这种形式的转义字符。
2.12 常量以什么样的二进制代码存储在计算机中
- 整数以补码的形式转化为二进制代码存储在计算机中的。
- 实数以IEEE754标准转化为二进制代码存储在计算机中的。
- 字符的本质实际也是与整数的存储方式相同。
2.13 代码规范化
推荐阅读《高质量程序设计指南C/C++编修》
让别人和自己更容易可读和理解
2.14 什么是字节
- 字节就是存储数据的单位,并且是硬件所能访问的最小单位。
- 1字节(Byte) = 8位(Bit)
2.15 什么是ASCII码
- ASCII不是一个值,而是一种规定。
- ASCII规定了不同的字符是使用哪一种整数值去表示。
- GB2312和UTF-8规定不同字母用什么数字表示。
运算符和表达式
1. C语言运算法简介
算术运算符:+,-,*,/,%
-
除法/的运算结果和运算对象的数据类型有关
两数都是int,则商是int 除数和被除数只要有一个/两个是浮点数,则商是浮点型
-
取余%的运算对象:两个操作数必须是整数(int类型),结果是整除后的余数
其余数的符号与被除数相同
例如:13%-3==1;-13%3==-1;3%5==3;-3%5==-3;1%25=1 当被余数小于余数时,结果就是被余数
举例:
要得到一个三位数456的个位、十位、百位 个位数:456%10=6 十位数:456/10%10=5 百位数:456/10=4
关系运算符:>,<,>=,<=,!=,==
位运算符:>>,<<,~,&,|,^
-
&——按位与
1&1 = 1 1&0 = 0 0&1 = 0 0&0 = 0
#include <stdio.h> int main(void) { int i = 5; int j = 7; int k; k = i&j; //表示5的二进制0101与7的二进制0111相与,结果是0101还是5 printf("%d\n", k); k = i&&j; printf("%d\n", k); //k的值只能是1或0,因为&&是逻辑运算符 return 0; } //结果为: 5 1 --------------------------------
-
|——按位或
1|0 = 1 1|1 = 1 0|1 = 1 0|0 = 0 例如:5|2等价于00000101|00000010,结果为00000111
#include <stdio.h> int main(void) { int i = 3; int j = 5; int k; k = i | j; printf("%d\n", k); return 0; //结果: 7 -------------------------------- }
-
~——按位取反
~i就是把i变量所有的二进制位取反
-
^——按位异或
-
规则是参与运算的两个运算符中相对应的二进制位上,若数相同,则该位的结果为0,否则为1
相同位0,不同位1 1^0 = 1 0^1 = 1 1^1 = 0 0^0 = 1 例如:s=32,s^=32,printf("%d", s), 相同为假不同为真,结果是0 3^2等价于00000011^00000010,结果为00000001,十进制是1
-
<<——按位左移
i<<1表示把i的所有二进制位左移一位,右边补0. 左移n位相当于乘以2的n次方 例:i = i * 8 i = i << 3 速度快
-
>>——按位右移
i>>3表示把i的所有二进制位右移3,左边一般都是0 有移n位表示除以2的n次方(不能溢出导致数据丢失)
位运算符的现实意义:通过位运算符我们可以对数据的操作精确到每一位
逻辑运算符:!,||,&&
-
C语言对真假处理
非0是真 真表示1 0是假 假是0表示 例如:(!a == 0)<==> ((!a) == 0)因为!的优先级高于== 它表示是a不等于0时为真 (a == !0)表示的是a等于1的时候为真
-
C语言真假判断
&&左边的表达式为假,右边的表达式肯定不执行 ||左边的表达式为真,右边的表达式肯定不执行
-
&&逻辑与也叫并且
-
逻辑运算符的结果只能是0或1
条件运算符:?:
格式:A ? B : C
等价于 if(A) //当表达式A为真,执行表达式B
B;
else //当表达式A为假,执行表达式C
C;
指针运算符:&,*
赋值运算符:=
-
作用:将一个数值赋值给一个变量或将一个变量的值赋给另一个变量。
-
赋值表达式一般形式:变量名=表达式/变量/值
注:左边不能是表达式,右边可以是赋值表达式 a+b=c 错误的 a=b=7+1 正确的 a=7+1=b 错误的
-
复合赋值表达式:+=、-=、*=、/=、%=
例子:t *= sum +12 <==> t = t * (sum + 12) x-=x+x <==> x=x-(x+x) +的优先级高于-= 5=4+1错误,常量5不能被赋值 举例:已有变量a=9,计算表达式a+=a-=a+a的值 1. 先计算a+a=18 2. 计算a-=18(此时a任然是9) a=a-18,a变为-9 3. 计算a+=-9(此时a是-9) a=a+-9 ,a变为-18
逗号运算符:,
逗号表达式:用逗号运算符将几个表达式连接起来
格式:A;B;C;D;……
功能:从左到右依次执行,最终表达式的值是最后一项的值
例如:j=2;i=(j++,++j,j+2,j-3) 最终i=3
(i=3,i++,++i,i+5)从左向右计算,i++之后i为4,++i之后i为5,i+5之后值为10,
所以表达式的值为10,i为5
字节运算符:sizeof
sizeof(类型):是静态得到字节数,不会影响下面语句
强制运算符:(类型名)(表达式)
利用强制类型转换符将一个表达式转换为前面所执行的数据类型
一般形式:(类型名)(表达式)
例子:(float)(5) 最终值为5.000000
初等运算符:圆括号(),下标运算符[ ],结构体成员运算符->
单目、双目、三目运算符
单目就是这个运算符只对一个变量进行操作
代表符号:!(逻辑非) ,~(按位取反),++(自增),--(自减)
双目就是这个运算符对两个变量进行操作
三目就是这个运算符对三个变量进行操作
代表符号: ?: (条件运算符)
自增、自减运算符
作用:自增运算符++,使运算变量的值增1,自减运算符--,使运算变量值减一,均是单目运算符
分类:前自增++i,后自增i++
相同点:最终都使i的值加一
不同点:前自增整体表达式的值是i加1之后的值;后自增整体表达式的值是i加1之前的值
注意:for循环条件语句中出现++i或者i++都是一样的,没有区别,
因为条件语句for(i++; ; )中是执行完第一个条件之后,再执行第二个条件语句
与赋值运算符=不同,
例子:
int a = 10;
printf(a++) = 10;
printf(a) = 11;
printf(++a) = 12;
printf(a) = 12;
visit(*p++); 第一次先输出*p,下一次才输出*p+1
compare(*p++, e)是先比较*p与e的值,第二次才是用*p+1和e的值进行比较
k++是先取K的值再将k的值自增1
为什么会出现自增:
- 代码更精炼
- 自增的速度更快 i++>i=i+1
学习自增前要明白的几个问题
- 我们编程是应该尽量屏蔽掉前自增和后自增的区别
- i++和++i单独成一个语句,不要把它作为一个完整复合语句的一部分使用。
2. 运算符的优先级和结合性
优先级:初等>单目>算术>关系>逻辑>条件>赋值>逗号
结合性:左结合性->单目,条件,赋值,扩展;其余右结合性
举例:a+b+c*(d+e)执行顺序为:
因为圆括号的优先级最高,乘法运算符次之,加法运算符最低,同优先级中加法结合性:从左向右结合
所以:先执行a+b的r1,再执行(d+e)得r2,在执行c*r2的r3,最后执行r1+r3得表达式结果
3. 图示运算符优先级和结合性
最高级:出现同级别运算符时的结合方向是从左往右
第二级:这一级都是单目运算符号,这一级的结合方向是从右向左。
出现 * p++,这时 * 和++同级别,先算右边,再左边。所以 * p++等价于 * (p++)
第三级:这一级都是算术运算符,结合顺序和数学学习中一致的,先乘除取余数,后加减。
第四级:这是左移、右移运算符,位运算时可能需要用到。
第六级:这三个符号也是位运算符号,其中内优先级,&>^>|。
第七级:逻辑与&&优先级大于逻辑或||。
第八级:也称为条件运算符号,是C语言中唯一的一个三目运算符,结合顺序是从右往左。
第九级:这些运算符也叫做赋值运算符,除此之外,>>=、<<=、&=、^=、|=这些赋值运算符也在这一级别内,结合顺序是从右往左。
最低级:逗号运算符也称为顺序求值运算符,在C语言中,运算级别最低。
4. 表达式
当运算符两侧的运算数据类型不一致时,结果与精度高的保持一致
C语言一些琐碎的知识
1. 浮点数的存储所带来的的问题
float和double都不能保证可以精确的存储一个小数。
例如:有一个浮点型变量x,如何判断x的值是否为0
if(|x-0.000001|<=0.000001)
是
else
不是
float i=99.9
printf("%f\n", i); 最终的结果为:99.900002
为什么循环中更新的变量不能定义为浮点型呢
- 因为浮点型不一定能准确存储。
2. 二进制全部为0的含义——0000000…00000的含义
-
表示数值零
-
表示字符串结束标记 ‘\0’
-
空指针NULL
NULL本质也是0,而这个零不代表数字零,而表示的是内存单元的编号为零
(表示0地址,即编号为零)我们计算机规定了,以零为编号的存储单元的内容不可读,不可写
-
0地址
在现代OS中,多进程中,OS给程序虚拟地址空间,所有程序在运行时都以为自己从0开始的一片连续空间(32位机器,顶是4G)
一般0地址不能碰的,在这种情况下,用0地址表示特殊事情- 返回指针无效
- 用NULL表示0地址
3. 什么叫分配内存,什么叫释放内存
分配内存:
-
OS把某一块内存空间的使用权限分配给该程序
释放内存: -
OS把分配给该程序的内存空间的使用权限收回,该程序不能够再使用这一块内存空间
注:释放内存不是把该内存的内容清零
4. 变量为什么必须得初始化
不初始化,则变量通常就是垃圾值