一、C语言介绍
丹尼斯.里奇和肯.汤普逊于1971~1973年在贝尔实验室,在开发UNIX操作系统时,以BCPL语言为基础研发了一款高级编程语言,取BCPL的第二个字母作为名字,所以叫C语言。
它是为了开发操作系统而研发的一款编程语言,它特别擅长控制硬件,所以在服务器、驱动编程、单片机、嵌入式使用较多。
C语言的优点:
1、语法简单,只有32个关键字,入门简单。
2、执行速度快能媲美汇编语言的执行速度,也适合用于实现算法。
C语言的缺点:
1、需要对内存、操作系统有一定的了解,易学难精。
2、由于出现的较早个人计算机还没普及,所以在设计时没有为普通人考虑太多,有一些语法上的陷阱和缺陷。
3、没有大型的软件公司在背后支持,可用的软件库比较少。
二、第一个C程序
#include <stdio.h>
int main(int argc,const char* argv[])
{
printf("Hello World!\n");
return 0;
}
编译执行:
# 编译代码,生成可执行文件,如果代码没有错误,会生成a.out可执行程序。
gcc hello.c
# 执行程序
./a.out
详解Hello World:
1、程序员所编写的代码并不是标准C代码,它需要一段程序把它翻译成标准的C代码,负责翻译的程序叫预处理器,被翻译的代码叫预处理指令,所有的预处理指令都是以#开头。
2、#include 就是预处理指令,它的功能是导入一个辅助文件到当前文件。
#include "filename.h" // 用于导入自定义的头文件
#include <filename.h> // 用于导入系统提供的头文件(标准库、操作系统)
3、C语言标准委员会为C语言以函数形式提供了一些基础功能,这些函数被封装在libc.so库文件中,并且提供了一些辅助说明文件,常用的有:stdio.h、stdlib.h、string.h等。
4、stdio.h头文件是 stdandard input output的缩写,里面对输入输入数据的函数进行了说明,当我们在代码中使用输入、输出数据的函数时,就需要包含该文件。
5、在C语言中,函数是管理代码的最小单位,一个函数就一段具有某项功能的代码,main函数是C程序默认的执行入口,入口有且仅有一个,无论它定在任何位置都最先执行。
6、函数名后面的内容是函数调用者传递给函数的数据,在命令行执行程序时,后面可以附带一些数据传递给main函数。
7、函数前面的内容是一种数据类型,它表示函数的执行结果是什么类型的数据,int指的就是整型。
8、C语言中使用大括号划分代码区域,被大括号包含的代码都属于main函数的函数体。
9、printf/scanf是标准库的函数,用于输出、输入数据,一般用于调试程序。
// printf在输出数据会有一些不方便输入的字符有特殊的字符表示(转义字符): \n 换行 \r 回到行首 \b 退格 \t 制表符 %% 输出一个% \\ 输出一个\
10、return语句有两项功能,结束函数的执行,当执行到retunr语句时,那怕后面还有代码都不会再执行了,另外可以返回一个数据给函数的调用者,main函数的调用者就是操作系统。
11、main函数的返回值表示了程序的结束状态: return 正数,表示程序执行出现异常。 return 0,表示程序正常结束。 return 负数,表示程序执行出现错误。 echo $? 查看main函数的返回值
12、C代码中以分号作为一行代码的结束标志,如果代码过长可以换行。
三、编译器和gcc
1、什么是编译器
它是一个负责翻译代码的程序,它负责把人类能看懂的代码翻译成计算机能理解的二进制指令,它由预处理器、编译器、汇编器、链接器组成,统称为编译器。
gcc 是GNU组织为了编译Linux内核代码而开发一款C语言编译器。
2、gcc编译器把C代码变成可执行程序的过程
1、把程序员所编写的代码进行预处理
# 把预处理的结果显示到屏幕上
gcc -E hello.c
# 会生成以.i结尾的预处理文件
gcc -E hello.c -o hello.i
2、把预处理的结果翻译成汇编代码
# 会生成以.s结尾的汇编文件
gcc -S hello.i
3、把汇编代码翻译成二进制指令
# 会生成以.o结尾的目标文件
gcc -c hello.s
4、把若干个文件目标文件、库文件合并成可执行文件
# 默认会生成a.out可执行文件·
gcc a.o b.o c.o ...
# 也可以使用-o指定可执行文件的名字。
gcc hello.o -o hello
# 执行程序
./hello
# 编译执行:
gcc a.o b.o c.o && ./a.out
注意:gcc hello.c 就包含了以上四个步骤,了解这个步骤是我们后续学习预处理指令、多文件编程、静态库、动态库的基础。
3、gcc编译器常用的参数(了解):
-E 预处理
-S 生成汇编文件
-c 生成目标文件
-o 设置编译结果的名字
-I 设置要导入的头文件的路径
-l 设置要链接的库名,例如:使用sqrt、pow等数学函数时就需要链接数学库 -lm
-L 设置要链接的库的路径
-D 在编译时定义宏
-g 编译时添加调试信息,这样编译出的程序可以用gdb调试。
-Wall 显示所有警告,编译器会以更严格的标准检查代码
-Werror 把警告当错误处理
-std 指定编译器要遵循的语法标准,c89,c99,c11,当前系统默认的是c99标准。
4、C语言的文件类型(了解):
.h 头文件,里面是一些对.c文件的说明
.c 源文件,里面是一些功能型代码
.i 预处理文件
.s 汇编文件
.o 目标文件
.gch 头文件的编译结果,用于检查自定义的头文件是否有语法错误,建议立即删除
.a 静态库文件,相当Windows系统下的.lib文件
.so 动态库文件,相当Windows系统下的.dll文件
四、C语言中基本的数据类型
C语言为什么要把数据分为不同的类型:
数据存储计算机中需要耗费存储空间(内存、硬盘),在编程语言中把数据按照使用范围、特点划分为不同的种类,解决什么问题使用什么类型的数据,这样可以节约存储空间、提高运算速度,这是程序员的基本功。
整型:
有符号整型:
最高位的二进制用于表示正负,0代表正数,1代表负数。 由于有符号整型使用较多,所以编译器默认signed可以省略,不加就代表加。
类型名 占用内存字节数 取值范围
signed char 1 -128 ~ 127
signed short 2 -32768 ~ 32767
signed int 4 -2147483648 ~ 2147483647
signed long 4|8
signed long long 8 -9223372036854775808 ~ 9223372036854775807
无符号整型:
所有的二进制位都用来表示数据,只能表示正数。
类型名 占用内存字节数 最大值
unsigned char 1 255
unsigned short 2 65535
unsigned int 4 4294967295
unsigned long 4|8
unsigned long long 8 18446744073709551615
无符号类型一般用于计数,unsigned 不能省略,使用起来比较麻烦,标准库为了让我们方便使用,在stdint.h 头文件中对它们进行的类型重定义,分别是:
uint8_t uint16_t uint32_t uint64_t size_t
浮点型:
小数点是浮动的,也就是带小数点的数据,采用科学计算数法存储,由符号位+指数域+小数域组成,这种存储格式,运算速度慢、又不太准确,所以尽量少用。
单精度: float 4
双精度: double 8
高精度: long double 12|16
注意:小数点后六位有效。
字符型:
char 字符型,字符就是符号或图案,但在计算机中以整数形式存储(使用整数模拟字符),当需要显示时根据ASCII表中的对应关系显出相应的符号或图案。
bool 布尔型:
C语言中没有真正的布尔类型,因为先有的C语言后出现的布类型,在C89之后以打补丁的方式新增布尔类型,需要包含stdbool.h头文件。
bool 1字节
true 4字节 实际使用的是整数1
false 4字节 实际使用的是整数0
注意:复合数据类型结构、联合、枚举、指针,后期会详细讲解。
五、变量
什么是变量:
在程序运行过程可以变化的量,它是存储数据的容器,需要先定义才能使用。
定义变量:
数据类型 <变量名>;
1、变量的所占用的内存字节数、存储数据的范围、使用的规则都由变量的类型决定。
2、变量的定义就是操作系统把一个标识符与内存建议映射关系,操作系统不会对这个内存进行初始化,所以变量的默认值是不确定的,我们要对一些特殊用途的变量进行初始化,比如:计数、求和、平均、累加。
3、定义的变量,出了它所在的大括号就不能再使用了。
4、定义变量后,操作系统会帮我们把变量名与一块内存建立映射关系,操作系统并不会帮我们把这块内存进行初始化,所以变量的默认是随机的、不确定的,特殊用途的变量要进行初始化为0,比如:计数、求和、累加。
变量的取名规则:
1、由数字、字母、下划线组成。 2、不能以数字开头。 3、不能与关键字重名(C语言中有32个关键字)。 4、见名知义,类型、功能、使用范围、归属模块。
特殊的合法的变量名:
bool,printf,true,flase
非法变量名举例:
1num、*num、auto
变量的使用:
num = 0; // 被赋值、存储数据,相当是个容器 num*3/4; // 参与运算,此时变量名就代表它存储的数据
变量的输出:
在实际项目中变量没有输入、输出的需求,但在调试阶段,我们为了检查程序的运行是否正确,一般使用printf函数输出变量的值,模拟把数据显示在界面上,使用scanf输入变量的值,模拟从界面获取用户输入的数据。
#include <stdio.h>
int printf(const char *format, ...);
功能:它是C标准库函数,把若干数据输出到终端上
format:提示信息+变量的类型信息+转义字符
...:若干个变量名,变量名之间有逗号分隔
返回值:printf输出到终端上的字符个数,一般不使用
在C语言中使用占位符来表示变量的类型:
%hhd %hd %d %ld %lld 有符号整型的占位符
%hhu %hu %u %lu %llu 无符号整型的占位符
%f %lf %Lf 浮点型的占位符
%c 字符型的占位符
布尔类型是用来逻辑运算的,不参与输入、输出,如果要强行输出,把它当整型即可。
变量的输入:
int scanf(const char *format, ...); 功能:从终端读取数据赋值给变量 format:占位符,提示信息和转义字符不要使用 ...:若干个变量的地址,&变量名可以计算出变量的地址 返回值:成功读取数据的变量个数,一般不使用 错误的使用案例: scanf("%d\n",&num);
六、常量(了解)
常量就是程序运行过程中不能改变的量,C语言的常量有:字面值常量、宏常量、枚举常量。
100 int 100l signed long 100ll signed long long 100u unsigned int 100lu unsigned long 100llu unsigned long long 3.14 double 3.14f float 3.14F long double 'A' char
七、运算符
算术运算符:
+ 加 - 减 * 乘 / 除 进行除法运算得到的结果是商 % 求余 进行除法运算得到的结果是余数
注意1:
整数/整数 计算的结果没有小数点,例如:5/3 的结果是1,3/5的结果是0。
注意2:
/和% 都是进行除法运算,/结果是商,%结果是余数,它们除数不以为零,会出现浮点数例外的错误,该错误会让程序立即结束。
关系运算符:
> 大于 >= 大于等于 < 小于 <= 小于等于 == 相等 != 不相等
注意1:
它们运算结果是逻辑值,C语言中的逻辑值是用0(假)和1(真)模拟的,计算出的结果还能进行数学运算。
注意2:
与数学中的用法不同,10 < x < 100,在数学中表示是x的取值范围,但在C语言中它就是需要运算的表达式,会先计算 10 < x 得到0|1,然后再把这个结果与100比较,所以该表达的结果永远是真。
注意3:
使用==运算符时,非常容易出错误,容易漏写一个=,该错误编译器检查不出,阅读代码也很难查出来,所以在使用==时,把常量放在左边,变量写右边,这样当漏写=时,编译器会报错误。
int num;
if(num = 100) // 变成了赋值语句,num被赋值为100,并且if分支永远执行
{
}
if(100 = num) // 用变量给常量赋值,编译器会报错
{
}
逻辑运算符:
&& 与 双目运算符,左右两边都是真,结果才是真 || 或 双目运算符,左右两边只要有一个是真,结果就是真 ! 非 单运算符,只针对右边的运算对象,求反,如果运算对象是真,结果就是假,如果运算对象是假,结果就是真 注意:!比&&、||的运算优先级高。它们会把运算对象转换成逻辑值再运算,零值转换为假,非零值转换为真,它们的运算结果也就是逻辑值。
短路特性:
当左边的值已经可以确定运算结果时,右边的不再计算,适当的使用可以实现精简的if结构,能看懂即可,不要这样写,因为它会影响代码的可读性。
(表达式1) && (表达式2) 如果表达式1结果是假,表达式2不再计算。
(表达式1) || (表达式2) 如果表达式1结果是真,表达式2不再计算。
num >10 && (num=0); // 等价于下面的if语句
if(num > 10)
{
num = 0;
}
自变运算符:
前自变:++/--i 变量的值立即加1或减1。 后自变:i++/-- 变量的值加1或减1,下一行代码有效。该类运算符可以让变量的值自加或自减1,但只能针对变量使用。
注意:
不要在复杂表达式中过度使用自变运算符,不同的编译器对它的运算规则不同,编译器会把合适的后自变优化成前自变。
赋值运算符及扩展:
= += *= /= %=... 是变量先运行后赋值的一种精简写法 a += b; 等价 a = a + b; a *= b; 等价 a = a * b;
三目运算符:
[A] ? [B] : [C];
它的运算对象有三个,所以叫三目运算符,先把[A]转换成逻辑值,为真执行[B],为假执行[C],相当于精简的 if else 结构。
注意:
与 if else 不同的是三目运算符必须有运算结果,所以它里面不能使用流程控制语句,比如:return、break、continue。
字节数运算符:
sizeof它不是函数,是C语言的32个关键字之一,它能计算出数据在内存需要占用的内存字节数,如果运算对象不是表达式,它可以不使用小括号。
注意:
sizeof(表达式)不会执行小括号里表达式,它只时推算表达式的执行结果是什么类型,然后计算出该类型在内存中占用多少个字节。
位运算符:
& | ~ ^ << >> 它们就针对数据的补码进行运算,后续再详细讲解。
八、类型转换
前提:只有相同类型的数据才能在一起进行运算,因为不同类型的数据,字节数不同、格式、运算规则不同,必须把不同类型的数据转换成同一类型才能运算。
自动类型转换(隐式类型转换):
不同类型的数据组成的表达式,编译器会把先它们转换成相同的类型再计算,这叫作自动类型类型或隐式类型转换,它们的转换规则是以不丢失数据为前提: 1、字节数不同,字节少的向多的转换。 2、整型向浮点型转换。 3、有符号的向无符号转换。 4、char、short会先转换成int类型,如果不能满足运算条件再转换成其它。
#include <stdio.h>
int main()
{
/*
unsigned short num1 = 10;
short num2 = -100;
printf("%d\n",sizeof(num1+num2));
if(num1+num2 < 0)
printf("小于0\n");
else
printf("大于0\n");
*/
unsigned int num1 = 10;
int num2 = -100;
printf("%d\n",sizeof(num1+num2));
if(num1+num2 < 0)
printf("小于0\n");
else
printf("大于0\n");
}
强制类型转换:
(目标类型)数据,会把数据强制转换为目标类型,这种转换方式有可能会造成数据丢失,慎重使用。
九、if语句
代码的默认执行流程是从上到下,逐步、逐条执行的,if语句可以根据判断条件选择让代码是否执行,改变了代码 的默认执行流程,所以这种语句也叫流程控制语句。
if(条件) // 单分支
{
// 当条件为真时,执行此处代码,如果此处代码只有一行,大括号可以省略,但在商业项目中建议不要省略,因为这样会影响代码的可扩展性
}
if(条件) // 双分支
{
// 当条件为真时,执行此处代码
}
else
{
// 当条件为假时,执行此处代码
}
if(条件1) // 多分支
{
// 当条件1为真时,执行此处代码
}
else if(条件2) // 可以有多个else if
{
// 当条件2为真时,执行此处代码
}
...
else
{
// 当条件1、条件2都为假时,执行此处代码
}
注意:如果if、else的代码块只有一行代码,大括号可以省略,但省略大括号会降低代码的可扩展性,降低安全性。