C语言是一门面向过程的计算机编程语言,与C++、C#、Java等面向对象编程语言有所不同。C语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、仅产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。C语言描述问题比汇编语言迅速、工作量小、可读性好、易于调试、修改和移植,而代码质量与汇编语言相当。C语言一般只比汇编语言代码生成的目标程序效率低10%-20%。因此,C语言可以编写系统软件。 [2]
当前阶段,在编程领域中,C语言的运用非常之多,它兼顾了高级语言和汇编语言的优点,相较于其它编程语言具有较大优势。计算机系统设计以及应用程序编写是C语言应用的两大领域。同时,C语言的普适较强,在许多计算机操作系统中都能够得到适用,且效率显著。 [3]
C语言拥有经过了漫长发展历史的完整的理论体系,在编程语言中具有举足轻重的地位。
第一个C语言程序:
- #include
- int main() {
- printf("Hello World~");
- }
char // 在 Java 中 char 占2个字节,在 C 中 char 只占1个字节
short // 短整型,占2字节
int // 整型,占4字节
long // 长整型,占4/8字节
long long // 更长的整型,占8字节
float // 单精度浮点数,占4字节
double // 双精度浮点数,占8字节
生活中的有些值是不变的(比如:圆周率、性别、身份证号码、血型等等)
有些值是可变的(比如:年龄、体重、薪资)
不变的量,C语言中用 常量 的概念来表示,变的值 C语言 中用 变量 来表示
局部变量
全局变量
当局部变量和全局变量同名的时候,优先使用局部变量
作用域:
作用域(scope),程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用的
而限制这个名字的可用性的代码范围就是这个名字的作用域
1、局部变量的作用域是遍历所在的局部范围
2、全局变量的作用域是整个工程
生命周期:
变量的生命周期指的是变量的创建到变量的销毁之间的一个时间段
1、局部变量的生命周期是:进入作用域生命周期开始,出作用域生命周期结束
2、全局变量的生命周期是:整个程序的生命周期
- #define _CRT_SECURE_NO_WARNINGS 1
- #include
-
- int main() {
- int a = 0;
- scanf("%d",&a);
- printf("a = %d",a);
- return 0;
- }
这里可以看到在代码的第一行有这样一段代码:#define _CRT_SECURE_NO_WARNINGS 1
如果不加的话会报这样一个错误,如下图所示:
咱们的 vs编译器人为 scanf 不安全,所以他建议你用 scanf_s ,但是我建议不要使用,因为scanf是c#提供的,而 scanf_s 是 vs编译器 提供的,如果你用了 scanf_s编译器 那我们如果将代码放到另一个编译器上,比如说gcc上的时候会无法识别导致编译失败,那么我们的代码就变得不具有跨平台性 / 可移植性了。
所以我建议大家 在代码的第一行加上 #define _CRT_SECURE_NO_WARNINGS 1 这么一句话就可以无视这个警告了
那么每次写代码都要加这一行代码太麻烦了,这里告诉大家一个一劳永逸的方法,如下:
我用的是 vs2022版本(其他版本的文件夹路径可能有些不一样~)
找到 VCProjectItems文件夹,并且打开newc++file.cpp文件,加上#define _CRT_SECURE_NO_WARNINGS 1 这句话,保存关闭即可,
但是这个文件需要管理员权限才可以修改,大家如果觉得麻烦就先在桌面创建一个记事本文件修改其内容也就是加上那句话保存关闭,然后再把文件命名改成newc++file.cpp再放回VCProjectItems文件夹替换掉原来的即可。
在C中常量用const关键字修饰,常量的本质还是变量,只不过是一个拥有常属性的变量;对比学习Java用的是 final,常量只能赋值一次不可更改。
在C中数组定义的长度只能是常量不能是变量,这是因为C语言在运行之前需要向系统申请分配空间,分配好之后不可更改所以数组的长度不能是变量(当然在C#中有一个malloc()函数,这里先不做介绍,他可以做到动态分配内存)
而在Java中有new关键字,用new来申请分配空间,是动态的分配空间所以在Java中数组的长度可以用变量来定义
枚举常量:
enum关键字
- #define _CRT_SECURE_NO_WARNINGS 1
- #include
-
- enum Color {
- RED,
- YELLOW,
- BLUE
- };
-
- int main() {
- enum Color color = RED;
- printf("%d", color);
- return 0;
- }
-
输出的是 0 ,在enum中他们的常量值分别是:RED是0,YELLOW是1,BLUE是2
在C#语言中没有 String 数据类型,但是我们可以用数组来代替字符串数据类型:
- int main() {
- char arr[] = { "abcdefghijk" };
- return 0;
- }
如果我们的字符数组中像下面这样定义的话,需要在数组的末尾加上 '\0' 表示结束,否则C#没有识别到结束符会一直往下搜寻 会乱码 数组长度也会异常 (当然这个'\0' 不占数组长度)
- #define _CRT_SECURE_NO_WARNINGS 1
- #include
-
- int main() {
- char arr[] = {'a','b','c','d','\0'};
- printf("%s",arr);
- return 0;
- }
C#语言函数的定义方式和Java差不多,只不过由于C#是面向过程编程所以不存在public、private这样的修饰关键字
- int max(int a, int b) {
- return a > b ? a : b;
- };
-
- int main() {
- max(1,2);
- return 0;
- }
然后函数调用的时候也不需要 new 一个对象来调用,直接调用就可以了
strlen() 计算字符串的长度,长度为11:
- int main() {
- char arr[] = { "abcdefghijk" };
- int length = strlen(arr);
- printf("%d",length);
- return 0;
- }
sizeof() 用来计算占用内存大小:
- int main() {
- char arr[] = { "abcdefghijk" };
- int size = sizeof(arr);
- printf("%d", size);
- return 0;
- }
这里因为字符串末尾会以 \0 转移字符来结束,所以计算字符串存储大小的时候是 12 而不是 11
if、else分支语句、while循环语句和Java相同就不多说了,直接上代码:
- #define _CRT_SECURE_NO_WARNINGS 1
- #include
- #include
-
- int main() {
- int num = 0;
- while (true) {
- if (num>10) {
- break;
- }
- else {
- printf("while循环执行到第 %d 遍了\n", num);
- num++;
- }
- }
- return 0;
- }
操作符的运用,这里的知识和Java中差不多也不做过多的介绍了,直接上运算符:
运算操作符:
+ - * / %
移位操作符:
>> <<
位操作符:
& | ^
赋值操作符:
= += -= &= /= &= |= ^= >>= <<=
单目操作符:
! 逻辑反操作,正变假,假变真
- 负值
+ 正数
& 取地址
sizeof 操作数的类型长度(一字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置 --
++ 前置、后置 ++
* 间接访问操作符
(类型) 强制类型转换
双目操作符:
不过多解释了就是操作符两边都有操作数的操作符比如说+加号 -减号等等......
三目操作符:
表达式1 ? 表达式2 :表达式3 (如果表达式1为真,则整个表达式的结果为表达式2,如果表示式为假则整个表达式的结果为表达式3)
逗号表达式:
exp1,exp2,exp3,......expN (这里只需要了解用逗号也可以隔开表达式就可以了)
下标引用、函数调用和结构成员:
[] () . ->
正整数的 原、反、补都是一样的,
负数的原码符号位不变其他位取反得到反码,反码再加1得到补码
在我们的计算机中存储的都是 补码 ,虽然说正数的补码就是本身,但是我们就是统一这个说法嘛~都是补码
我们规定有符号数 的第一位为符号位,负号为1 , 正号为0,该符号为也占一个 bit
比如说 int 类型 4byte——32bit 那么符号位占第一位,剩下的31位为数值大小
auto:
auto关键字指的是自动的意思,我们的局部变量会在花括号开始时候被自动创建花括号结束后会被自动销毁,那么其实局部变量就是自动变量,在定义局部变量时 C#默认在前面加上了一个auto ,比如 auto int a = 0; 但是这个 auto 被省略了可以不写,默认局部变量就是自动变量
break:
在循环中会用到来结束循环,或者 switch case 中也会用到拿来结束
const:
用来修饰常变量
continue:
在循环中表示继续
default:
默认
extern:(在main函数中表示引入外部符号,定义的全局变量只要用extern关键字引入都可以使用)
首先在 test.c 中定义一个变量testData = 2022:
然后在 test2.c 中用 extern 关键字引入,如下:
运行完全可以输出 testData = 2022
register(寄存器关键字):
先来给大家介绍一下什么是寄存器,存储数据的地方有 硬盘、内存、高速缓存、寄存器,那么如下图所示 -> 越往上走 存储器 的 访问速度就越快、造价就越高所以内存空间也就越小,不然一台电脑得多少钱啊~
以前的计算机存储数据 有 内存 和 硬盘,这时 内存 和 cpu 处理的的速度还是比较搭配的,内存能拿多快我 cpu 就能处理的多快,但是经过时间的推移计算机的发展 cpu 的处理速度变得越来越快,但是内存的访问速度却跟不上,这个时候就算CPU的性能再高也无法提高计算机的速度,所以出现了 高速缓存 和 寄存器。
先让内存 的数据加载到高速缓存,然后高速缓存的数据再加载到寄存器中去,然后cpu再去从寄存器中拿取数据,当cpu从寄存器中拿不到数据的时候再去依次逐层往下到高速缓存、内存中去拿取,但是每次拿取数据的首选都是寄存器
那么在代码中如何去使用这个关键字呢?-> 如下所示:
- #define _CRT_SECURE_NO_WARNINGS 1
- #include
-
- int main() {
- register int a = 10;
- return 0;
- }
比如说我觉得这个 变量a 的使用频率会很高每次都要去内存中拿太慢了,我们只要在定义变量的前面加上 register关键字 ,就会将该变量放到寄存器之中(当然这也只是建议 编译器 将该变量放到寄存器中去,因为如果定义了很多个这样的变量不可能所有的变量都能放到寄存器中,所以最后还是由编译器去决定到底将哪个变量放到寄存器中去)
signed:
比如我们声明一个变量 int a = 10;那么在 int 前面默认是添加了一个 signed 关键字的,代表他是有符号数,那么与之对应的还有一个关键字 ->
unsigned:
如果我们在声明变量前面加上一个 unsigned关键字,如 unsigned int a = -10; 那么代表这是一个无符号数字,不管是正数还是负数,在 C# 看来都是正数。
struct:
结构体关键字
typedef:
顾名思义就是类型定义,这里应该理解为类型重命名,代码如下所示:
- #define _CRT_SECURE_NO_WARNINGS 1
- #include
-
- int main() {
- unsigned int a = 10;
- typedef unsigned int un_int;
- un_int b = 10;
- printf("a = %d",b);
- return 0;
- }
以上代码我们先定义了一个 a ,但是发现这个 unsigned int 定义很麻烦,所以用 typedef 关键字将 unsigned int 重命名为 un_int ,然后用un_int 又去定义了变量 b,通过测试可以知道 变量a 和 变量b 的类型是相同的。
当然 typedef关键字 不止这点应用场景,后面再详细介绍......
union:
联合体/共用体
volatile:
这是一个体现我们C语言段位的关键字,哈哈~
static:(static关键字可以修饰 局部变量、全局变量、函数方法)
先来说说static修饰的局部变量:
没有 static 修饰的局部变量生命周期 -> 在花括号开始时被创建 -> 当执行到花括号结束时局部变量被销毁~
但是当我们用static关键字修饰局部变量之后的生命周期 -> 变长了 -> 不再是到达花括号结束就被销毁了,而是程序结束的时候才会被销毁
当static修饰的全局变量:
如果一个全局变量没有用 static关键字 修饰的话,只要用 extern 声明之后便可以使用,
但是如果我们在全局变量前用 static关键字 修饰的话,那么无论用不用 extern 声明,该变量只能在自己所在的源文件内部中使用,其他源文件无法使用该变量,也就是说 static 改变了全局变量的作用域
当static修饰的函数:
如果一个函数没有用 static关键字 修饰的话,只要用 extern 声明之后便可以使用,
但是如果我们在函数前用 static关键字 修饰的话,那么无论用不用 extern 声明,该函数只能在自己所在的源文件内部中使用,其他源文件无法使用该函数 -> 也就是说 static 改变了函数的作用域(但是这个说法不太准确,准确的来说是改变了函数的链接属性,一个函数正常来说是有外部链接属性的,当被 static 修饰之后外部就看不到他了,此时他的外部链接属性 -> 就变成了 -> 内部链接属性)