C语言介绍
C语言诞生于1971~1973年,美国贝尔实验室,肯.汤姆逊和丹尼斯.里奇(C语言之父)
C语言是专门为了编写操作系统而诞生的语言,天生适合对硬件编程,以运行速度快而著称,而且C语言非常适合编写数据结构和算法
C语言出现时间过早,所以有很多缺陷,因为没有想到普通人也可以编程,但是很多前辈总结了避免这些缺陷或陷阱的经验
建议学习书籍:《C语言三剑客》->《C陷阱与缺陷》 《C与指针》《C程序设计语言》
C语言的语法非常自由,自由源于自律,自由意味着危险
C89语法标准,旧的系统中默认的标准
C99语法标准,对C语言的扩展和增强 -std=gnu99
C11语法标准,全新的升级
C语言的第一个程序:
//导入头文件
#include <stdio.h>
//程序员所编写的代码,不是真正的标准C代码,需要一段程序翻译成标准C代码,负责翻译的程序叫做预处理器,翻译的过程叫做预处理,被翻译的语句叫做预处理指令,以#开头的语句都是预处理指令
// #include 的功能是把一个头文件导入(拷贝)到当前位置
//#include <xxx.h>:从系统指定的路径下查找并加载头文件
//#include "xxx.h":先从当前工作路径查找,如果找不到再从系统指定的路径查找并加载
//头文件:以.h结尾,里面存储的是一些辅助性的代码,绝大多数都是函数的声明
//C语言标准委员会为C语言一函数形式提供的一些基础功能,这些函数都被封装在libc.so(标准c库)
//然后通过很多的头文件对不同的功能的标准库函数进行了说明,stdio.h就是其中之一,常用:stdlib.h
//string.h
//源文件:以.c结尾,里面存储的是实际的功能性代码
int main()
//int 是一种数据类型,表示main函数的执行结果是一个整数值 (echo $?)
//C语言中函数是管理代码的最小单位,一个函数就是一个功能的实现
//main函数是整个程序的入口,有且只有一个
//main函数是由操作系统调用的,所以它的返回值给了操作系统,反映了程序是如何结束的
{
printf("Hello World!\n");
//printf/scanf是标准库的输出输入函数,一般用于调试代码
//转义字符:键盘上有一些不好直接表示的符号,通过一些特殊的字符搭配来表示,这种字符搭配称为
//转义字符
// \n 换行; \t 制表符,Tab键; \a 铃响;
// \b 光标后退一个字符;printf("\b \b") 退格
// \r 光标回到行首; %%显示一个%; \\显示一个\
//注意:C语言是以分号作为一行代码的结束标志,使用大括号来划分代码
return 0;
//return的作用:
//1.返回一个执行结果给函数的调用者
//0:一切正常 正数:出现异常(别人导致) 负数:出现错误(自己导致)
//2.提前结束函数
//main函数遇到return语句程序结束
}
编译器
负责把人看的懂的文本文件,翻译成计算机能看的懂的二进制文件,过程叫做编译。
由预处理器、编译器、链接器组成。
gcc编译器是GNU社区为了编译Linux内核代码而开发的一款免费的编译器,默认语法标准是C99或者C89
常用编译参数:
-E:只显示预处理的结果
-S:生成汇编文件
-c:只编译不链接
-o:指定编译结果的名字
-I:指定头文件的加载路径 -I path
-l:指定要使用的代码库 -lm导入数学库
-Wall:尽可能多的产生警告信息
-Werror:把警告当错误处理
-std:设置编译语法标准 -std=gnu89/99
C代码如何从源文件变成可执行文件的过程:
1.预处理:把源文件翻译成预处理文件
gcc -E xxx.c 显示预处理结果到终端
gcc -E xxx.c -o xxx.i 把预处理的结果存储到xxx.i预处理文件中
2.编译:把预处理文件翻译成汇编文件
gcc -S xxx.i 生成以.s结尾的汇编文件
3.汇编:把汇编文件翻译成二进制的目标文件
gcc -c xxx.s 生成以.o结尾的目标文件
4.链接:把若干个目标文件合并成一个可执行文件
gcc a.o b.o c.o ... 生成a.out可执行文件
C语言中的文件类型:
.c 源文件
.h头文件
.h.gch 头文件的编译结果文件,会被优先使用
.i 预处理文件
.s 汇编文件
.o目标文件
.so共享库文件
.a 静态库文件
C语言
一、数据类型
1、现实中的数据就是自带类别属性的
2、对数据进行分类可以节约内存存储空间,提高运行速度
存储空间的单位:
Bit 比特 存储一个二进制位,只能存储0或者1,是计算机存储数据的最小单位
Byte 字节 存储八个二进制位,是计算机存储数据的基本单位
Kb 1024字节
Mb 1024Kb
Gb 1024Mb
Tb 1024Gb
Pb 1024Tb
C语言中数据分为两大类:
自建(程序员自己设计的)、内建(C语言自带的)
sizeof:可以计算类型/变量所占内存字节数
内建:
整型:
signed 有符号:二进制位最高位作为符号位 0正数 1负数
类型名 字节数 数据范围
signed char 1 -128~127
signed short 2 -32768~32767
signed int 4 +-20亿
signed long 4(操作系统32位)/8(操作系统64位) +-20亿/+-9开头的19位整数
signed long long 8 +-9开头的19位整数
注意:signed不加就代表加了 char == signed char
unsigned 无符号:二进制位最高位作为数据位
类型名 字节数 数据范围
unsigned char 1 0~255
unsigned short 2 0~65535
unsigned int 4 0~40亿
unsigned long 4(操作系统32位)/8(操作系统64位) 0~40亿/0~1开头的20位整数
unsigned long long 8 0~1开头的20位整数
注意:由于定义无符号数据时比较麻烦,标准库中把这些类型重新定义成了新的类型名;
需要包含头文件<stdint.h>
uint8_t uint16_t uint32_t uint64_t //数字代表bit数
浮点型:
类型名 字节数
float 4 单精度浮点型
double 8 双精度浮点型
long double 12(操作系统32位)/16(操作系统64位)
注意:采用科学计数法、在二进制与真实数据之间需要进行换算过程,因此浮点数的运算使用速度比整型要慢得多,所以编程时尽量使用整型
注意:大多数操作系统只对小数点后6位有效
模拟型:
字符型:char
整数类型占位符:%hhd 字符类型占位符:%c
字符:就是图案或符号 ,字符在内存中依然存储成整数,需要显示成字符时,操作系统会根据ASCII码表中的对应关系,把整数显示成对应的符号或图案
'\0' 0 特殊字符 空字符
'0' 48
'A' 65
'a' 97
布尔型:bool
先有C语言,后有的bool类型,C语言中不可能有真正的布尔类型,都是在<stdbool.h>中对布尔类型使用整数进行模拟
二、变量与常量
什么是变量:
在程序运行期间值可以发生变化的叫做变量,相当于存放数据的的盒子
定义:
类型名 变量名;
int num;
取名规则:
1、由字母、数字、下划线组成
2、不能以数字开头
3、不能与C语言32个关键字重名
数据类型相关:
内建类型:
char short int long void float double 7
自建类型:
struct union enum sizeof 4
类型限定符:
auto const static volatile register typedef extern signed unsigned 9
流程控制相关:
分支:
if else switch case default 5
循环:
for while do 3
跳转:
break continue goto return 4
4、见名知意(功能、类型、范围)
注意:变量的初始值默认是随机的,为了安全起见,一般会在定义时初始化为0
使用:
赋值: 变量名 = val;
运算: 变量名*10
变量的输入、输出:
int print(const char *format,...);
功能:输出数据
format:“提示信息+占位符”
...:变量名列表
返回值:成功输出的字符个数
类型占位符:C语言通过占位符的方式传递变量的类型
signed + | char | short | int | long | long long |
占位符: | %hhd | %hd | %d | %ld | %lld |
unsigned + | char | short | int | long | long long |
占位符: | %hhu | %hu | %u | %lu | %llu |
float | double | long double | 字符型:char |
%f | %lf | %LF | %c |
int scanf(const char *format,...);
功能:输入数据
format:“占位符”
...:变量地址列表
注意:scanf需要变量类型和地址
变量地址 == &变量名
返回值:成功输入的变量个数
什么是常量:
在程序运行期间数值不变化的叫做常量
10 默认int类型
10l long类型
10ll long long类型
10u unsigned int类型
10lu unsigned long类型
10llu unsigned long long类型
3.14 默认double类型
3.14f float类型
3.14l long double类型
三、数据的格式化输出
%nd:显示n个字符宽度,不足时补空格,右对齐
%-nd:显示n个字符宽度,不足时补空格,左对齐
%0nd:显示n个字符宽度,不足时补0,右对齐
%n.mf:显示n个字符宽度(小数点也算一位),不足时补空格,右对齐;m表示小数点后显示的位数(四舍五入)
%g:不显示小数点后多余的0;
四、运算符
自变运算符:
++/-- 让变量的值自动+1或-1
前自变:++/--num 立即生效
后自变:num++/-- 下一行语句才生效
注意:不要在一行代码内多次运用自变运算符
算术运算符:
(+ - / * %)
整数/整数 只保留整数部分
/ % 除数不能为0,否则会在运行时出现浮点数例外(核心已转储)
% 不能对浮点数求余
关系运算符:
== ! = > < >= <=
会得到比较的结果是1(成立)或0(不成立),比较的结果还可以继续参与运算
int n = -10;
10 < n < 100; //永远为真
注意:常量建议放在 == != 的左边,防止少写一个=
逻辑运算符:
&& || ! (单目)
会先把运算对象转换成逻辑值:非零转为真,0转为假
A && B 一假即假
A || B 一真即真
!A 求反
&&和||的短路特性:当左边部分的值已经可以确定整个逻辑运算符的结果时,那么右边部分不执行;也可以借助短路特性实现简单的单分支效果
三目运算符:
A ? B : C(printf("three:%d\n",num < 9 ? 88:99))
判断A的值,如果为真(非0)则执行B,否则执行C
赋值运算符:
= += -= *= ......
a+=b;a=a+b;
注意:赋值运算符的运算结果是右边赋的数据
位运算符:
二进制下才能位运算
<< >> & | ~ ^
& 按位相与(有0为零,全1为1)
| 按位相或(有1为1,全0为0)
~ 按位求反
^ 按位异或(相同为0,不同为1)
<< 按位左移n位 (左边超出的丢弃,右边补0)
>> 按位右移n位 (右边超出的丢弃,左边补符号位)
sizeof:
也是一个运算符
注意:运算符是有优先级之分,如果使用时记不住的话,多加小括号
单位 算数 位 关系 逻辑 三目 赋值
五、类型转换问题
只有相同类型的数据才能进行运算,如果类型不相同的数据需要先转换成相同类型才能运算
自动类型转换:
转换规则:以不丢失数据为基础,可以适当地牺牲一些空间
1、字节少的向字节多的转
2、有符号的向无符号的转
3、整型向浮点型转
特例:当运算对象类型只是char 或者short且类型不同,编译器会做类型提升,提升为int 再运算
强制类型转换:
(新类型名)数据;
有丢失数据的风险,但是需要使用时还是得用
六、if分支语句
if(表达式) // 单分支语句
{
// 表达式的值为真(非0),则执行此处代码
}
if(表达式) // 双分支语句
{
// 表达式的值为真(非0),则执行此处代码
}
else
{
// 表达式的值为假(0),则执行此处代码
}
if(表达式1) // 多分支语句
{
// 表达式1的值为真(非0),则执行此处代码
}
else if(表达式2)
{
// 表达式2的值为真(非0),则执行此处代码
}
......
else
{
// 如果所有表达式都为假,最后执行此处代码
}
七、switch开关语句
switch(n) // n可以是数值、表达式,结果必须是整型
{
//default:......;break;// 先非法判断
case val1:.....;break; // val必须是常整数,如果n等于val,则打开执行开关;break关闭执行开关
case val2:.....;break;
......
default:......; // 如果所有case都不打开,则最后打开此开关;无论位置在哪,都会最后执行
}
// switch不可以与continue配合
// 注意:case n1 ... n3:表示在范围[n1,n3]内,会打开case开关,但是这属于GNU编译器的特有语法,不建议使用
八、for循环语句
循环:
就是一种让一段代码反复执行的方式,从而达到想要的效果
for循环一般会使用一个变量来引导循环的进行,这个变量叫做该循环的循环变量 i index
for循环的变化很灵活,但也很危险(相比于其他循环)
for([1];[2];[3])
{
[4];
}
[1]、给循环变量赋初值 C99或C11标准才可以在此处定义变量(改~/.vimrc)
在for循环内定义变量,只能在for内使用,出了for循环后无法使用for循环内定义的变量,会屏蔽循环外定义的同名变量
[2]、判断循环变量是否到达结束边界值。如果判断为假,那么结束循环;反之,继续循环
[4]、被反复执行的代码,称为循环体
[3]、改变循环变量的值,防止出现死循环,一般对循环变量自加或自减
顺序:1、2、4、3、2、4、3、2......2
for的多种写法:
for(;;)
{
// 死循环
}
int i=0;
for(;i<10;i++)
{
}
for(int i=0;i<10;)
{
if(xxx)
{
i++;
}
}
等
for循环的大括号问题:
1、for循环内只有一段代码时,大括号可以省略,但是这样不利于后期代码扩展,一般的商业代码要求大括号不能省略
2、建议上下对齐,括号内改缩进的要缩进
九、while循环语句
当条件为真时执行循环体,为假时结束循环
while(条件)
{
// 循环体
}
// 相当于
for(;条件;)
{
}
// while循环相当于for循环其中一个精简版本
// for循环是负责解决明确知道循环次数的问题
// while循环是负责只知道循环结束条件而不确定循环次数的问题
十、do-while循环语句
do{
// 循环体
}while(条件);// 分号不能少
// 先执行循环体,在判断循环条件,至少会执行一次
// 适合先干活,再判断的特殊情况,例如:输入密码
十一、循环嵌套
循环语句中有循环语句
外层循环执行一次,内层循环执行n次
十二、跳转语句
goto:
可以在函数内任意跳转
标签名:
xxx:
goto 标签名;
注意:goto很容易破坏已经设计好的分支或者循环语句,绝大多数公司时禁止使用goto的、goto在驱动编程和硬件编程中非常适合处理异常
break:
1、在switch语句中关闭case开关
2、跳出循环,如果有循环嵌套时,只能跳出最近一层循环
continue:
结束本次循环,直接进入下一次循环
return:
1、返回一个返回值给函数的调用者
2、提前结束函数,程序回到调用位置继续执行
十三、数组
什么是数组:
变量的组合,是一种批量定义相同类型变量的方式
定义:类型名 数组名[数量];
注意:数组的长度一旦确定,无法改变
使用:数组名[下标];
下标:从0开始,范围从0~数量-1
遍历:把数组的数据从头到尾显示或访问
一般与for循环配合,把循环变量 i 当作数组下标
初始化:
类型名 数组名[数量] = {1,2,3,4,5,...};
1、数组与变量一样,默认值随机,所以一般都要先初始化
2、数组不能整体初始化,只能逐个初始化
int arr[20] = 0; //错误!
3、这种初始化的语法只能在定义数组时使用
4、初始化数据过多,编译器会产生警告并丢弃多余数据
5、初始化数据不足,编译器会自动补0
6、初始化数据可以全部省略,但是要写{ },相当于全部成员初始化为0
7、如果有初始化数据,则可以省略数组数量,编译器会自动统计数据的个数,然后确定数组的数量
计算数组的总字节:
sizeof(arr)
计算数组成员的字节数:
sizeof(arr[0])
计算数组长度:
sizeof(arr)/sizeof(arr[0])
数组越界问题:
为了程序的运行效率考虑,C语言不检查数组下标是否越界
数组越界的后果:
1、一切正常
2、段错误(核心已转储)
3、脏数据
二维数组
一维数组相当于把变量排成一排,通过编号来访问
二维数组相当于把变量排成一个矩阵,通过行号和列号访问
二维数组在内存中依然是连续存储的
定义:
类型名 数组名[行数] [列数];
int arr[4] [5];
使用:数组名[行下标] [列下标];
行下标:0~行数-1
列下标:0~列数-1
遍历:一般需要与双层for循环配合,外层循环负责遍历行,内层循环负责遍历列
初始化:类型名 数组名[行数] [列数]={{第一行},{第二行},...};
注意:1、大括号内数据可以全部省略,那么就自动补0;
2、大括号数据和列数不省略,行数可以省略,编译器会自动计算行数
3、不能省略二维数组的列数
变长数组
定义数组时,使用变量作为数组的长度,这种数组称为变长数组
特点:在代码编译期间数组的长度是不确定的,当执行到数组的定义语句时长度才最终确定下来,并且一旦确定,长度无法改变
优点:可以根据实际情况来确定数组的长度,从而节约内存
缺点:初始化发生在编译期间,而可变长数组的长度的确定发生在运行期间,因此可变长数组无法初始化