大家好,我是Edison
其实最近在备考、刷题过程中,发现C语言中的某些知识点竟然遗忘了;
从而导致又跑去翻书、翻笔记等,很浪费时间,于是乎,就打算把「C语言」整个系列从头到尾全部梳理一遍;
一方面,方便自己查阅,省去了看笔记、看书的时间;
另一方面,也给各位老铁提供了一篇查阅的资料,欢迎前来指正;
Let’s get it!
送所有正在努力的大家一句话:你不一定逆风翻盘,但一定要向阳而生
基本了解C语言的基础知识,对C语言有一个大概的认识。
每个知识点就是简单介绍,不做详细讲解;
后续会慢慢的把每个章节全部拿出来重点说明;
C语言是一门通用计算机编程语言,广泛应用于底层开发。
C语言是一门面向过程的计算机编程语言,与C++,Java等面向对象的编程语言有所不同。
C语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。
计算机语言顾名思义就是 人和计算机交流的语言;
即人给计算机发送指令,计算机读懂以后按照指令执行我们想要计算机执行的工作。
常用的计算机语言有:C、C++、JAVA 等等。
我们现在使用的电脑,俗称为:硬件;
但是要让电脑工作就必须要给电脑装一个操作系统,比如:Windows、Linux 等;来进行对硬件的操作。
操作系统和硬件由 驱动层 相关联。因此,硬件、驱动和操作系统就叫做底层,所以我们写一个操作系统、驱动软件等这些操作就叫做底层开发。
在底层往上就是应用层,即我们能直观感受到的东西,比如应用软件:QQ、微信。
C语言也可以用于应用软件开发,但是相比其他语言就显得很劣势。
最初的编程语言我们称为 机器语言,即 二进制的语言,要想实现某一个功能就要写好多二进制的序列。
由于太难理解后来出现了 汇编语言,汇编语言用助记符代替机器指令的操作码,我们用助记符就可以更加容易写代码。
后来发展出了更加容易的 B语言和C语言,更加贴近我们人的逻辑。
但后来由于C语言的标准不统一,这就导致A公司的代码在B公司上就无法运行,因此制定了ANSI C,即C语言国际标准。这些标准约束了C语言的语法规则。
由于计算机中只有二进制数字,一些符号没法表示;
例如,像a、b、c、d这样的52个字母(包括大写)以及一些常用的符号(例如*、#、@等),因此约定一套编码来表示这些符号,这就是ASCII码表。
编译器主要有Clang、GCC、WIN-TC、SUBLIME、MSVC、Turbo C等。
编译器就是编译代码的工具,用来将我们写的C代码转换成电脑能读懂的二进制;
需要注意的是:我们常用的VS2019、Dev C++、VSCode等;这些严格来讲并不能称为编译器,它们是由编译器组成的集成式开发环境。
我用的编译器是:VS2019
主函数是C语言的程序入口,程序开始执行的位置。
一个源文件当中只能有一个主函数,要不然编译器就不知道从哪个入口开始执行了。
主函数代码示例
#include
int main()
{
printf("hello Edison\n");
return 0;
}
运行结果
代码解释
int
是函数返回值的类型,最常用的是整型;
main
是主函数的函数名;
()
是函数的参数,由于主函数一般情况下为无参,因此()
里面的内容可以为空;
{}
里面是主函数的内容;
return 0;
表示函数的返回值;
printf
是C语言提供的库函数,它的具体用法是printf(“要输出的内容”)
;
#include
是printf
库函数的头文件;
stido
翻译为标准输入输出;
我们的输入和输出函数都在这个头文件里面,每次要使用这些函数都要引用这个头文件。
我们在对一件事物的描述需要各种各样的数据;
比如价格、体重、身高等描述就要用到整数、小数等数据类型。
因此C语言中引入了这些类型
在计算不同数据类型大小之前首先要清楚计算机中的单位;
计算机中最小的单位为bit(比特),即 存放一个1或者0的空间。
计算机是一个硬件,通电后,可以识别电信号,0和1代表两种逻辑状态,1代表电路连通状态,0代表电路断开状态;
存放一个0或者1的空间大小为1个比特位。
详见的存储单位如下:
计算机在存储数据和执行指令的时候是以8个比特(bit)为单位的,也就是1个字节(byte);
我们可以用
sizeof
操作符来计算不同数据类型在内存中所占空间的大小,单位是字节(byte);可以看出,不同的数据类型所占空间大小是不同的;
我们可以使用不同整形和浮点型,防止空间浪费;
比如
short
短整型的二进制换算成十进制,可以表示的范围是-32768~32767
;
int
整型范围是-2147483648 ~2147483647
,因此在描述年龄等较小的数据时,选择short
短整型可以很好地节约空间。
单精度浮点数占4个字节,双精度浮点数占8个字节。
单精度浮点数有效数字8位,双精度浮点数有效数字16位。
在使用浮点型时也可以使用不同的类型来节约内存。
不同的数据类型有不同的输出格式
%d - 打印整形
%c - 打印字符
%f - 打印浮点型 - 小数
%p - 以地址形式打印
%x - 打印16进制数字
%s - 打印字符串
不同的类型可以用多种格式打印
#include
int main()
{
int a = 32;
printf("%x", a);//结果为20
return 0;
}
%x
是16进制输出,因此在打印输出时,会将a
转换为16进制再进行输出。
常量,顾名思义就是不能改变的量,在生活中常量有很多,比如圆周率、身份号码等。
变量,就是值可以被改变的量,比如体重、年龄、薪资等。
在C语言中,我们一般用
数据类型 + 变量名 + 赋值操作符(=) +变量的值
这种形式来定义一个变量。
比如我们要定义一个整形变量age,它的值为10,
int age = 10;//age值为10
//但由于age是变量,因此age的值可以被重新赋值
age=11;//此时age的值变为11
同理要定义一个浮点型变量 weight 和 字符型变量ch,也是一样
float weight = 45.5f;
char ch = 'w';
注意
在C语言中,
=
并不是我们数学中的等号,而是一个赋值操作符;
int age = 10
意为:定义一个整形变量age,将10赋值给age,此时age的值就是10;
这个过程我们称为给age赋初值,如果不给age赋初值,age将无法使用,因为编译器也不知道age的值是多少。
还可以这样做
//方法一
int age=10;
//方法二
int age;
age=10
变量分为:局部变量和全局变量
全局变量:定义在代码块
{}
之外的变量;
局部变量:定义在代码块
{}
之内的变量;
#include
int a = 100;//全局变量
void test()//定义一个函数,后面会具体讲解函数
{
int b = 100;//局部变量
}
int main()
{
int c = 10;//局部变量
return 0;
}
总结✨
全局变量与局部变量名相同,局部变量优先。
但是在写代码过程中不建议相同,因为会造成不必要的错误
比如
#include
int global = 2019;//全局变量
int main()
{
int local = 2018;//局部变量
//下面定义的global会不会有问题?
int global = 2020;//局部变量
printf("global = %d\n", global);
return 0;
}
上面的局部变量
global
变量的定义其实没有什么问题的!
结果应该为局部变量的值,也就是
global = 2020
;
作用域(scope),程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效或者可用的;
而限定这个名字的可用性的代码范围就是这个名字的作用域;
1、局部变量的作用域是变量所在的局部范围。
2、全局变量的作用域是整个工程。
举个栗子
#include
int b = 20;
int main()
{
int c = 30;
{
int a = 10;
}
printf("a=%d", a);//打印会报错 无法打印输出
printf("b=%d", b);//可以打印输出
{
printf("c=%d", c);//可以打印输出
}
return 0;
}
分析
a是局部变量,a的定义范围是:a所在的那个
{}
;它的作用域就是:它所在的{}
,出了{}
以后a就不能使用了;
b是全局变量,作用域是整个工程,因此b的作用域并不受
{}
的限制。
c是局部变量,c的作用域是整个
main
函数所在的范围,由于printf(“c=%d”, c);
所在的{}
也属于main
函数所在的范围,因此可以打印输出。
变量的生命周期指的是变量的创建到变量的销毁之间的一个时间段;
1、局部变量的生命周期是:进入作用域生命周期开始,出作用域生命周期结束。
2、全局变量的生命周期是:整个程序的生命周期。
C语言中的常量和变量的定义的形式有所差异。
C语言中的常量分为以下以下几种:
字面常量
也就是程序中出现的数字,字符等;
代码示例
10 和 w 就是字面常量。
#include
int main()
{
int a = 100;
char b = 'h';
return 0;
}
const
修饰的常变量
const
修饰的常变量无法修改a不是常量,只是具有常属性,不能被修改而已
#define
定义的标识符常量
define
定义的常量不能被修改
这种定义方式相当于给字面常量起别名,它的别名具有字面常量的属性,在使用时别名直接替换为字面常量;
代码示例
#include
#define MAX 100
int main()
{
int a = MAX;
printf("%d\n", a);
return 0;
}
但是有一点需要注意
#include
#define MAX 10
int main()
{
int arr[MAX] = { 0 };//相当于int arr[10] = { 0 };
MAX=20;//无法修改,因为a具有字面常量的属性;
return 0;
}
枚举常量
通过枚举关键字enum定义的常量;
这里了解一下就可以了,在此不作过多介绍
//括号中的male,female,secret是枚举常量
enum Sex
{
male, //男
female, //女
secret //保密
};
由双引号引起来的一串字符称为字符串字面值,或者简称字符串。
注:字符串相当于多个字符连续存放在字符数组中,但是字符串的结尾是一个隐藏的
\0
转义字符。
在计算字符串长度的时候
\0
是结束标志,不算作字符串内容。
\0
对于字符串非常重要,在打印字符串时,\0
可以使打印停止。
比如下面的代码
//"abcdef" - 字符串
//%s - 打印字符串的格式
//arr1表示数组,数组就是一组数
//int arr[] = { 1,2,3,4,5 };
#include
int main()
{
char arr1[] = "abc";
char arr2[] = { 'a', 'b', 'c' };
char arr3[] = { 'a', 'b', 'c', '\0' };
printf("%s\n", arr1);
printf("%s\n", arr2);
printf("%s\n", arr3);
return 0;
}
打印结果
为什么会这样呢?在监视中可以看到字符数组的内容:
从监视中看出arr1在结尾处比arr2多了一个
\0
,而arr1和arr3是一样的;
arr1字符串后面有一个隐藏的
\0
,因此在打印输出时,遇到\0
后,打印停止;
arr2字符串后面没有
\0
,因此在打印完abc以后还会继续打印,直到遇到\0
才会停止;
arr3字符串有
\0
,遇到\0
后,打印停止。
由于字符串中有一个隐藏的
\0
,因此字符数组的大小比我们输入的字符,多1个;
当字符数组的大小比我们存入数组的字符串大时,多出的空间会全部补为\0
假如我们要在屏幕上打印一个目录:c:\code\test.c
我们该如何写代码?
#include
int main()
{
printf("c:\code\test.c\n");
return 0;
}
实际上程序运行的结果是这样的
这里就不得不提一下转义字符了。
转义字符顾名思义就是转变意思。
下面看一些转义字符
假设我们要在屏幕上打印一个单引号'
,怎么做?
假设要在屏幕上打印一个字符串,字符串的内容是一个双引号"
,怎么做?
#include
int main()
{
printf("%c\n", '\'');//单引号
printf("%s\n", "\"");//双引号
return 0;
}
学会了吗?我们来看一道某大厂的笔试题吧
//程序输出什么?
#include
int main()
{
printf("%d\n", strlen("c:\test\328\test.c"));
return 0;
}
strlen这个函数可以求字符串的长度;在这里
\t
和\32
都是一个字符;
\t
是水平制表符算作一个字符可以理解,\32
表示一个八进制的数字;
你可能会问:为什么不是
\328
呢?那是因为8进制当中没有8;
编译器先将32转化成十进制也就是40,再从ASCII码表中找到40代表的字符,
\32
就相当于那个字符。注意:单个的
\
是转义字符,编译器发现没有\c
等的转义对应的字符所以忽略这个\
补齐字符
这个补齐字符很简单,我们直接看代码
#include
int main()
{
int a = 123;
int b = 1;
printf("%3d\n%3d\n", a, b);//%3d是右对齐,如果输出的位数不够三位,就会补空格
printf("%3d\n%-3d\n", a, b);//%-3d是左对齐
}
运行结果
1、代码中有不需要的代码可以直接删除,也可以注释掉
2、代码中有些代码比较难懂,可以加一下注释文字
举个栗子
#include
int Add(int x, int y)
{
return x + y;
}
/*C语言风格注释
int Sub(int x, int y)
{
return x-y;
}
*/
int main()
{
//C++注释风格
//int a = 10;
//调用Add函数,完成加法
printf("%d\n", Add(1, 2));
return 0;
}
注释有两种风格:
1、C语言风格的注释/*这是一个注释*/
;缺陷:不能嵌套注释
2、C++风格的注释
//这是一个注释
;它可以注释一行也可以注释多行,推荐使用这种;
顾名思义,就是根据不同的条件选择性地执行不同的语句。
如果你好好学习,校招时拿一个好offer,走上人生巅峰。
如果你不学习,毕业等于失业,回家卖红薯。
这就是选择!
代码示例
#include
int main()
{
int coding = 0;
printf("你会去敲代码吗?(选择1 or 0):>");
scanf("%d", &coding);
if (coding == 1)
{
prinf("坚持,你会有好offer\n");
}
else
{
printf("放弃,回家卖红薯\n");
}
return 0;
}
如果if里面的条件成立,就会执行if语句里面的代码;
如果不成立,则执行else里面的代码。
有些事必须一直做,比如我日复一日的学习,比如大家,日复一日的打游戏。
还比如:
C语言中实现循环的方式:
1、while语句
2、for语句
3、do … while语句
由于只是初识C语言,因此在这里只挑while循环来说明:
//while循环的实例
#include
int main()
{
printf("关注我\n");
int line = 0;
while (line <= 20000)
{
line++;
printf("我要继续努力敲代码\n");
}
if (line > 20000)
printf("赢取白富美\n");
return 0;
}
以上代码可以自己运行一下
当一段代码(比如两个数相加,相减)需要不断执行的时候,我们就可以将这段代码写成一个函数;
在需要执行这段代码的时候,只需调用函数就可以,以此来简化代码。
比如一个加法的函数
通常情况下我们是这样写的
#include
int main()
{
int num1 = 0;
int num2 = 0;
int sum = 0;
printf("输入两个操作数:>");
scanf("%d %d", &a, &b);
sum = num1 + num2;
printf("sum = %d\n", sum);
return 0;
}
但是可以用函数来实现
#include
int Add(int x, int y)
{
int z = x + y;
return z;
}
int main()
{
int num1 = 0;
int num2 = 0;
int sum = 0;
printf("输入两个操作数:>");
scanf("%d %d", &num1, &num2);
sum = Add(num1, num2);
printf("sum = %d\n", sum);
return 0;
}
函数的特点就是简化代码,代码复用。
要存储1-10的数字,怎么存储?
C语言中给了数组的定义:一组相同类型元素的集合
int 是数组的类型;
arr是数组的名字;
[10]
是数组的大小,数组的大小也可以不写,编译器会自动识别数组的大小;
{ }
内是数组的每个元素。
int arr[10] = {1,2,3,4,5,6,7,8,9,10};//定义一个整形数组,最多放10个元素
C语言规定数组的每个元素都有一个下标,下标是从0开始的。
数组可以通过下标来访问的。 比如:
int arr[10] = {0};
//如果数组10个元素,下标的范围是0-9
强调一点:要访问数组的元素,就要用到数组的下标,
比如
arr[i]
中,i 就是数组的下标。
数组下标是从0开始的整数,因此数组的第一个元素的下标为0而不是1。
以此类推,第二个元素的下标为1,第三个元素下标为2 …
#include
int main()
{
int i = 0;
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
顾名思义就是对变量进行操作的符号,比如加减乘除等,在此仅做简单的介绍。
+
:加
-
:减
*
:乘
/
:除
%
:取模(即求一个数除以另一个所得到的余数))
<<
:左移
>>
:右移
位移操作符移动的是变量的二进制位而不是十进制位
左移操作符:
<<
将二进制位的位数向左移动,移动以后移出位丢弃,右边补0,比如将数字1进行左移一位。
代码示例
#include
int main()
{
int a = 1;
int b = a << 1;
printf("%d", b);
return 0;
}
移位演示
打印结果
&
:与运算
|
:或运算
^
:异或运算
那怎么计算的呢?
它们都是对二进制位进行运算;
&
:只要有0就是0,相同为1才是1;
|
:只要有1就是1,相同为0才是0;
^
: 两个位相同为0,相异为1;
比如将5和3进行与运算:
#include
int main()
{
int a = 5;
int b = 3;
int c = 5 & 3;
printf("%d\n", c);
return 0;
}
演示:
=
+=
-=
*=
/=
&=
^=
|=
>>=
<<=
怎么理解呢?
=
:将右边的值赋值给左边;
+=
:对原本的操作进行了简化;
比如:
a += 1
等价于a = a+1
只是a += 1
更简便一些, 其他操作符也是同理;
这里单独说一下前置++和后置++
++
在前,先自加1,再使用;
a先自加1,再将自己的值赋值给b
++
在后,先使用,再自加1;
a先将自己的值赋值给b,再自加1
>
:大于
>=
:大于等于
<
:小于
<=
:小于等于
!=
:不相等
==
:相等
&&
:逻辑与,可以理解为 并且;
||
:逻辑或,可以理解为 或者;
也叫做三目操作符;
exp1 ? exp2 : exp3
代码示例
如下是一段普通的判断代码
#include
int main()
{
int a = 10;
int b = 0;
if (a == 5)
{
b = -6;
}
else
{
b = 6;
}
printf("%d\n", b);
return 0;
}
但是此代码可以用条件操作符来表示
#include
int main()
{
int a = 10;
int b = 0;
b = ((a == 5) ? (-6) : (6));
printf("%d\n", b);
return 0;
}
是不是简洁很多了?
exp1, exp2, exp3, …expN
整个表达式的结果是:最后一个表达式计算结束的结果
代码示例:
#include
int main()
{
int a = 1;
int b = 3;
a = (b = a + 1, a = b + 1, b = a + b);
printf("%d", a);
return 0;
}
分析:
先计算
a+1
的值然后将其赋值给b,此时b的值为2,a的值为1;
再计算
b+1
的值然后将其赋值给a,此时a的值为3,b的值为2;
最后计算
a+b
的值并将结果赋值给b,此时b的值为5;
整体表达式的结果就是最后一个表达式的结果,最后一个表达式的结果为5;
因此整体表达式结果为5;
[]
()
.
->
结构体会讲到,此处不过多讲解
C语言内置的关键字。只需初步了解即可。
另外记住,变量名不能和关键字相同,要不然编译器无法区分关键字和变量名。
typedef
顾名思义是类型定义,这里应该理解为类型重命名。
#include
//将unsigned int 重命名为uint_32, 所以uint_32也是一个类型名
typedef unsigned int uint_32;
int main()
{
//观察num1和num2,这两个变量的类型是一样的
unsigned int num1 = 0;
uint_32 num2 = 0;
return 0;
}
在C语言中:
static
是用来修饰变量和函数的;
1、修饰局部变量-静态局部变量;
2、修饰全局变量-静态全局变量;
3、修饰函数-静态函数;
修饰局部变量
代码示例
#include
void test()
{
static int i = 0;
i++;
printf("%d ", i);
}
int main()
{
int i = 0;
for (i = 0; i < 10; i++)
{
test();
}
return 0;
}
test
函数中的局部变量 i 在test
函数运行结束后并没有被销毁,每次调用test
函数都会使i自增;
另外在第一次调用
test
函数的时候 i 已经被赋予初值0了,出了函数并不会被销毁,此后继续调用函数static int i=0
这条语句都不会被执行;
也就是说i并不会被再次赋值为0。
为了更清晰地对比,我们可以观察把static去掉以后程序的运行结果
static
修饰局部变量改变了变量的生命周期,让静态局部变量出了作用域依然存在,到程序结束, 生命周期才结束。
修饰全局变量
一个全局变量被static修饰,使得这个全局变量只能在本源文件内使用,不能在其他源文件内使用。
修饰函数
一个函数被static修饰,使得这个函数只能在本源文件内使用,不能在其他源文件内使用。
效果与static修饰全局变量类似
#defne 宏名(被替换的内容)(要替换的内容)
代码示例:
//define定义标识符常量
#define MAX 1000
//define定义宏
#define ADD(x, y) ((x)+(y))
#include
int main()
{
int sum = ADD(2, 3);
printf("sum = %d\n", sum);
sum = 10 * ADD(2, 3);
printf("sum = %d\n", sum);
return 0;
}
运行结果:
要理解指针,首先要理解内存
内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的。
所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节(byte)。
为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址。
变量都有地址,取出变量地址如下:
#include
int main()
{
int num = 10;
#//取出num的地址
//注:这里num的4个字节,每个字节都有地址,取出的是第一个字节的地址(较小的地址)
printf("%p\n", &num);//打印地址,%p是以地址的形式打印
return 0;
}
&
操作符可以将num的地址取出,然后赋值给指针p。
但我们知道num占4个字节,因此它有4个地址,
&
取出的是num的起始位置地址。
运行结果
00F3FC5C
是第一个起始位置的地址;
那地址如何存储?需要定义指针变量。
int num = 10;
int *p;//p为一个整形指针变量
p = #
指针的使用实例:
#include
int main()
{
int num = 10;
int* p = #
*p = 20;
return 0;
}
解释说明:
*
是解引用操作符,*p
是通过p找到p指向的对象,即num;
p也是一个指针变量,它也有地址。
以整形指针举例,可以推广到其他类型,如:
#include
int main()
{
char ch = 'w';
char* pc = &ch;
*pc = 'q';
printf("%c\n", ch);
return 0;
}
总结如下:
我们取出一个变量的地址的时候,这个地址便称为 指针;
而这个地址如果要存起来的话,需要存到一个变量里面去,这个变量就被称为 指针变量;
指针变量可以对它进行 解引用 操作;
指针是一个变量,用来存放地址;
那么指针有没有大小呢?
#include
int main()
{
printf("%d\n", sizeof(char*));
printf("%d\n", sizeof(short*));
printf("%d\n", sizeof(int*));
printf("%d\n", sizeof(double*));
return 0;
}
答案肯定是有的,但是不同的平台,指针大小是不一样的;
比如,以 32 位平台为例,指针的大小就为4个字节
但是以64位平台为例,指针的大小就为8个字节
结论:指针大小在32位平台是4个字节,64位平台是8个字节。
结构体是C语言中特别重要的知识点,结构体使得C语言有能力描述复杂类型。
比如描述学生,学生包含:
名字+年龄+性别+学号
这几项信息。
这里只能使用结构体来描述了。
例如:
struct Stu
{
char name[20];//名字
int age; //年龄
char sex[5]; //性别
char id[15]; //学号
};
结构体的初始化:
#include
struct Stu
{
char name[20];//名字
int age; //年龄
char sex[5]; //性别
char id[15]; //学号
};
int main()
{
//创建一个学生s,并对其进行初始化赋值
struct Stu s = { "张三", 20, "男", "20180101" };
//. 为结构成员访问操作符
printf("name = %s age = %d sex = %s id = %s\n", s.name, s.age, s.sex, s.id);
//->操作符可以通过指针来访问到结构体的具体成员
struct Stu* ps = &s;
printf("name = %s age = %d sex = %s id = %s\n", ps->name, ps->age, ps->sex, ps -> id);
return 0;
}
我们通过结构体创建一个学生s,并对其进行初始化操作;
此时我们可以通过
.
(点操作符) 来访问结构体具体的成员;
具体语法为:
结构体变量名.结构体具体内容
,比如:s.name
;
我们也可以创建结构体指针来指向s,并通过
->
(箭头操作符) 来通过结构体指针来访问结构体成员;
具体语法为:
结构体指针->结构体内容
,比如:ps->name
;
作者水平有限,如有总结不对的地方,欢迎留言或者私信!
如果你觉得这篇文章还不错的话,那么点赞、评论、收藏就是对我最大的支持!
你知道的越多,你不知道越多,我们下期见!