C语言,是一门高级的、非常接近底层硬件的计算机编程语言,它通常作为计算机专业学学习的第一门语言,它具有结构严谨、语法简单、执行效率高、编译环境配置方便等诸多优点,你几乎不需要任何计算机基础知识就可以学会它。甚至通过这门语言,你可以了解到计算机操作系统、CPU、内存等硬件的相关知识,这些优点是它在嵌入式领域非常常用的原因之一。那么问题来了,学习C语言,你需要掌握的入门知识有哪些?
目录
1)方式一(图中arr1)不指定整形数组大小,用{value1,value2.......valuen}的方式进行声明
3)方式二(图中arr2)指定数组的大小用{value1,value2.......valuen}的方式进行初始化
1)方式一(图中str1)不指定字符数组大小,直接用一个字符串常量进行初始化。
2)方式二(图中str2)不指定字符数组大小,用{'字符1','字符2'.......'字符n'}的方式进行声明。
一.数据类型及其声明
1.整形
1)用途及其取值范围
整形用于存储正负整数,如:-1、0、1等,包括字符整形(char)、短整型(short)、整形(int)、长整形(long),而每种整形数还分为有符号和无符号:有符号整形是第一位存符号位,其它位存二进制值;无符号整形是所有的二进制位都用来存储值的大小。
不同类型的整形位数(bit)及其取值范围如下图所示:
(需要可自取)
2)声明方式
各种整形声明方式如下图所示:
2.浮点型
1)浮点型的用途及其取值范围
浮点型用于存储小数,必须包含符号位,如:3.1415926535、2.71828182846、-1.667等。浮点型包括单精度浮点型(float)以及双精度浮点型(double)。
取值范围如下图所示:
(需要可自取)
2)单/双精度浮点数声明方式
这里需要注意的是在给单精度浮点数赋值小数的时候要在后面加上一个小写f,表示这是一个单精度浮点数,否则编译器可能会把该小数当做双精度浮点数来处理。
二.格式控制符
1.格式控制符的用途
格式控制符是在标准输入输出函数中,用于控制数据的输入输出类型的字符。
2.各种常见格式控制符的含义
如下表:
格式控制符 | 控制类型 |
%d | 整形 |
%ld | 长整型 |
%u | 32位无符号整形 |
%f | 单精度浮点数 |
%lf | 双精度浮点数 |
%x | 8位十六进制数小写 |
%X | 8位十六进制大写 |
%s | 字符串 |
%p | 十六进制地址 |
%c | 字符 |
%o | 八进制 |
3.格式控制符在标准输入输出函数当中的实例用法
对于C语言初学者来说,常用的标准输入输出函数是scanf和printf,下面我以这两个函数作为示例,来展示格式控制符的用法
#include<stdio.h>
int main()
{
char name[255];
printf("请输入名字:");
scanf("%s", name);
printf("你的名字是%s", name);
return 0;
}
这个代码实现的效果就是用scanf函数把用户输入的字符串以字符串形式存储到字符数组name当中,然后再使用printf函数把字符数组当中的内容以字符串的形式打印出来。实现的效果如下图:
三.头文件的使用
1.为什么引入头文件
在C语言当中,我们无需关注底层的函数机制,使用C语言标准库为我们准备好的一些函数,就可以以快速实现我们想要实现的功能,把一些特定的标准库加入到我们的代码当中,需要的就是引入头文件。
2.如何引入头文件
1)引入编译器为我们准备好的C语言标准库的头文件
方式如下:
#include<stdio.h>//标准输入输出头文件 #include<time.h>//时间头文件 #include<errno.h>//标准报错头文件 #include<string.h>//字符串头文件
使用预处理指令#include< >,在< >中输入你想包含的头文件名即可。
2)引入我们自己写的头文件
方式如下:
使用预处理指令#include" ",在" "中输入你写的头文件的名字即可。
注意!!在引用自己写的头文件时,必须保证整个项目的头文件中有这个头文件。如果没有找到被引用的头文件,那么编译器会在标准库的头文件当中进行查找,如果仍旧没有找到,则报错。
就比如这里我把function.h从头文件删除,VS2022就提示了一下内容,而且此时编译器也是无法进行编译的。
四.操作符及其优先级
1.操作符的作用
在C语言当中,有非常多的操作符,而操作符实现的功能是对变量进行修改以及用于判断,如:给变量赋值(=)、让变量自增(++)/自减(--)、判断两个值十分相等(==)、判断某个表达式是否成立,如果成立执行一个表达式,如果不成立执行另一个表达式(exp1?exp2:exp3)
2.C语言具体拥有的操作符与其用法
操作符基于操作对象数目的分类可以分为单目操作符(操作对象数为一),双目操作符(操作对象数为二),三目操作符(操作对象数为三)
1)单目操作符、作用及其用法
单目操作符 | 作用 | 用法 |
++ | 变量自增 | i++(先使用,再自增)/++i(先自增再使用,--同理) |
-- | 变量自减 | i--/--i |
() | 调用函数,优先运算,强制类型转换 | function()/(exp)/(float)i |
[] | 调用数组元素 | arr[] |
-> | 指向结构体的成员变量 | Struct->var |
* | 解引用指针(空指针不能被解引用) | *p=1 |
& | 对变量进行取地址操作 | &i |
! | 对表达式执行逻辑非操作 | !exp |
. | 访问对象的成员变量 | Struct.var |
2)双目操作符、作用及其用法
双目操作符 | 作用 | 用法 |
= | 赋值 | int i=5 |
+= | 增加某个值 | i+=5 |
-= | 减少某个值 | i-=5 |
/= | 除以某个值 | i/=5 |
*= | 乘某个值 | i*=5 |
== | 判断a是否等于b,等于为1,不等于为0 | a==b |
!= | 判断a是否不等于b,不等于为1,等于为0 | a!=b |
|| | 判断两个表达式是否至少有一个为真 | exp1||exp2 |
&& | 判断两个表达式是否都为真 | exp1&&exp2 |
>/>= | 判断表达式a是否大于或者大于等于表达式b | a>b a>=b |
</<= | 判断表达式a是否小于或者小于等于表达式b | a<b a<=b |
^ | 对a和b进行逻辑按位与的操作 | a^b |
| | 对a和b进行逻辑按位或的操作 | a|b |
& | 对a和b执行逻辑按位且的操作 | a&b |
+ | a+b | a+b |
- | a减b | a-b |
* | a乘b | a*b |
/ | a除以b | a/b |
% | 让a对b取余 | a%b |
<< | 左移操作符,把a的二进制位往左移b位 | a>>b |
>> | 右移操作符,把a的二进制位往右移b位 | a<<b |
3)三目操作符、作用及其用法
三目操作符 | 作用 | 用法 |
?: | 表达式一成立吗?如果成立,执行表达式二,如果不成立,执行表达式三,整个表达式的值为最后执行表达式的值 | exp1?exp2:exp3 |
4)其他操作符
其他 | 作用 | 用法 |
, | 逗号表达式,执行逗号中所有的表达式,并且最终返回最后一个表达式的值 | exp1,exp2,exp3......expn-1,expn |
3.操作符的优先级
操作符优先级相关内容比较复杂且系统,需要程序员经验的积累和实践方可理解。碍于篇幅原因,本篇文章只讲两种操作符的优先级,分别是最高优先级和最低优先级的操作符。
1)优先级最高的操作符
最高优先级的操作符括号是括号:(),如果括号中嵌套了括号,那么就先执行嵌套最深的括号,然后执行第二深的括号的内容,以此类推。
2)优先级最低的操作符
优先级最低的操作符是逗号表达式,除逗号表达式外所有的操作符优先级都比逗号表达式要高,这是在C语言设计时规定的。
五.分支语句
1.if语句
1)适用情况
if语句适用于如果满足了特定条件就结束循环或者退出函数的情况
2)实例及讲解
下面实例一个简单代码:
int a = 10;
int b = 20;
if (a == b)
{
printf("%d", a + b);
}
代码的含义是如果表达式a如果等于b成立,那么执行if分支下花括号{}当中的代码块:打印a+b。
需要注意的是,如果if后面没有带花括号,那么if分支下的代码有且只有最靠近if的那条语句,这样解释可能有点抽象,我们举个代码示例来方便理解
int a = 10;
int b = 20;
//这里a不等于b,所以if分支下的语句不会被执行
//但是这里没有{},所以最靠近if的语句一不会执行
if (a == b)
printf("%d\n", a + b);//语句一(不执行)
printf("%d\n", a - b);//语句二(执行)
printf("%d\n", a * b);//语句三(执行)
printf("%d\n", a / b);//语句四(执行)
我们来看代码执行结果:
但是如果if后面跟了花括号,代码改成如下样子:
int a = 10;
int b = 20;
//这里a不等于b,所以if分支下的语句不会被执行
//但是这里有{},所以if{}中的语句一,语句二,语句三都不会执行
if (a == b)
{
printf("%d\n", a + b);//语句一(不执行)
printf("%d\n", a - b);//语句二(不执行)
printf("%d\n", a * b);//语句三(不执行)
}
printf("%d\n", a / b);//语句四(执行)
只有最后的语句四执行,最后的代码结果也是如此,后面的else和if一样,需要花括号才能包含多个语句,否则至多影响一条语句
(这里打印0的原因是整形相除只取整数部分,10/20=0.5,取整数部分0,所以最后打印0)
3)if之间的嵌套
if与if之间是可以嵌套的,如下图:
int a = 10;
int b = 20;
if (a > 0)//如果a大于0
{
if (b >= 0)//在a大于0的情况下判断b是否大于0
{
printf("a和b都大于0");
}
}
if与if之间的嵌套可以让我们实现一些高级的功能,比如计算一个年份是否为闰年,判断一个数是否为水仙花数等问题都是利用if的嵌套来求解的,后面的if-else等也是可以嵌套使用的。但是if嵌套的过深过多会导致代码的可读性下降,如果可以的话,尽量减少if语句嵌套的深度。
2.if-else语句
1)适用情况
if-else适合二元问题,非此即彼,如以下代码:
int main()
{
float f1 = 3.1415926;
float f2 = 2.718;
if (f1 - f2 >= 0)
{
printf("f1>=f2");
}
else
{
printf("f1<f2");
}
return 0;
}
代码中如果f1-f2>=0,那么打印f1>=f2;否则打印f1<f2,这就是一个典型的二元问题,而if-else的分支功能正好可以用来解决相关问题。
3.if-else if语句
1)适用情况
普通多分支,但不需要考虑其他情况的场景。
2)实例及其讲解
如代码所示:
int a = 15;
int b = 25;
if (a + b > 0)
{
printf("两数之和为正");
}
else if (a + b < 0)
{
printf("两数之和为负");
}
代码中使用if-if else语句对a+b之和进行判断,如果a+b>0,那么打印两数之和为正,如果两数之和为负,那么打印两数之和为负。
需要注意的是:只有当if当中表达式不成立的时候,才会对后面的else if按顺序依次进行判断,如果else if当中的语句范围本身就在if语句当中了,那么这个else if语句就是纯垃圾语句了。同时else if语句可以有多个,整体的代码结构如下:
int a = 15;
int b = 25;
int c = 35;
int d = 45;
int e = 15;
if (a == b)//a==b成立吗
{
printf("a=b");
}
else if (a == c)//如果if不成立,那么按顺序依次判断else if当中的语句
{
printf("a=c");
}
else if (a == d)//如果上一个else if不成立,那么判断这个else if
{
printf("a=d");
}
else if (a == e)//如果上一个else if不成立,那么判断这个else if
{
printf("a=e");
}
4.if-else if-else语句
1)适用情况
多分支情况下,同时考虑所有分支都不符合时的要进行操作的场景。
2)实例及其讲解
int a = 15;
int b = 25;
int c = 35;
int d = 45;
int e = 15;
if (a == b)//a==b成立吗
{
printf("a=b");
}
else if (a == c)//如果if不成立,那么按顺序依次判断else if当中的语句
{
printf("a=c");
}
else if (a == d)//如果上一个else if不成立,那么判断这个else if
{
printf("a=d");
}
else if (a == e)//如果上一个else if不成立,那么判断这个else if
{
printf("a=e");
}
else//如果所有的if和else if语句都不成立
{
printf("a与b,c,d,e当中的任何元素都不相等");
}
if-else if-else语句和if-else if的区别就在于对if和else if都不满足的情况进行了特定的操作,就比如上面的代码,对a与b,c,d,e都不相等的情况打印:"a与b,c,d,e当中的任何元素都不相等"。可以说就是if-else与if-else if结合,前面的if和else if是一个分支,后面的else是一个分支,如果前面的if和else if都不成立,那么就执行else。
5.switch-case语句
1)适用场景
switch case语句和if-else if-else有点类似,都是适用于多分支情况下,同时考虑所有分支都不符合时的场景。
2)实例及其讲解
有以下代码:
int a = 15;
switch (a)//要判断的值(必须是整形)
{
case 55://如果变量的值为55
printf("a=55"); break;
case 45://如果变量的值为45
printf("a=45"); break;
case 35://如果变量的值为35
printf("a=35"); break;
case 25://如果变量的值为25
printf("a=25"); break;
case 15://如果变量的值为15
printf("a=15"); break;
default://如果变量的值与上面的值都不相等
printf("a是其他值");break;
}
代码中switch()中括号中传的值a就是我们要进行判断的值,这个值必须为整形int,同时case 后面的值是用来判断a与这个值相不相等,如果相等就执行该分支,并且结束整个switch语句,否则判断下一个分支。如果所有的分支都没有被执行,那么执行default分支中的语句。
tips:通常switch语句会和enum枚举类型结合起来用,但是枚举不在本文的讨论范围之内,所以switch语句做到了解会用即可,不做深入讨论。
6.总结与分析
分支语句用于判断某些条件是否成立,以及成立时要执行一些操作的场景。同时分支语句支持嵌套,允许程序在某一分支上继续产生新的分支,形成分支树的结构。
对于if和else if的灵活运用,可以实现很多复杂的功能,但是太深的if嵌套,势必会造成代码可读性的下降,这需要程序员通过不断地写代码运用分支语句来把握代码的深度。
六.循环语句
1.for循环
1)整体格式
整体格式如下代码所示:
for(表达式1;表达式2;表达式3)
{
//代码块
}
表达式1为初始化表达式,只在循环开始时执行一次,此后不再执行,表达式2为判断表达,当表达式2成立时,循环继续。表达式3为调整表达式,在每次执行完代码块时,会执行表达式3,然后开始新一轮的循环,顺序为:
表达式1--->表达式2(成立)--->代码块--->表达式3(进行调整)
--->表达式2(成立)--->代码块--->表达式3(调整)
--->表达式2(成立)--->代码块--->表达式3(调整)
--->表达式2(成立)--->代码块--->表达式3(调整)
......
直到表达式二不成立或者有for循环当中执行了break时,循环结束。
2)示例
打印10~20的数组
int i = 0;
for (i = 10; i <= 20; i++)
{
printf("%d ", i);
}
执行结果如下
遍历一个二维数组
int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
int i = 0;
for (i = 0; i < 3; i++)//遍历行
{
int j = 0;
for (j = 0; j < 4; j++)//遍历列
{
printf("%d ", arr[i][j]);//打印
}
printf("\n");//换行
}
执行结果如下:
2.while循环
1)整体格式
while(表达式)
{
//代码块
}
2)讲解
先对表达式的值进行判断,如果表达式的值成立,则执行代码块的内容然后再判断表达式的值是否成立然后依次类推,顺序如下:
表达式(成立)--->代码块--->表达式(成立).......--->代码块--->表达式(不成立)--->结束循环
while循环非常适用于只知道跳出循环所满足条件的情况。
3)实例
int i = 10;
while (i)
{
printf("%d ", i);
i = i - 1;
}
小tips:在C语言当中,0表示假,非0表示真。
所以这里循环实现的效果就是当i不等于0的时候,打印i的值,并且让i自减1,然后进入下一轮循环。
3.do-while循环
1)整体格式
do
{
//代码块
}while(表达式);
2)循环过程与注意事项讲解
do-while循环是先执行代码块中的内容,再进行判断,顺序如下:
代码块--->表达式(成立)--->代码块.......--->代码块--->表达式(不成立)--->结束循环
适用于需要先执行一下代码块当中的代码进行预处理的循环。
在格式方面需要注意的是while(表达式)后面一定一定要加一个英文分号 ; 否则编译器会认为这句话还没结束,从而造成语法错误。
七.函数、数组、结构体的声明和使用
1.函数
1)函数的声明
格式如下:
函数返回值类型 函数名(参数类型1 参数1,参数类型2 参数2......参数类型n 参数n)
{
//代码块
}
函数必须要有的是返回值类型和函数名,如果不返回值,函数返回值类型也得使用void来表明它没有返回值,在函数内部,使用return来返回值。
函数的内部可以调用其他已经声明的函数,也包括它自己,自己调用自己的函数就是递归函数。
2)实例
代码:
int Add(int a, int b)
{
return a + b;
}
int Sub(int a, int b)
{
return a - b;
}
这里声明了两个非常简单的函数,实现两个整形的加法和减法功能。
如果我想实现一个比较复杂的功能,比如计算斐波那契数列的第n项,就可以使用递归函数,代码如下:
int Fib(int n)//n表示第几项
{
if (n == 1 || n == 2)return 1;//如果是第一项或者第二项,返回1
else return Fib(n - 1) + Fib(n - 2);//如果n!=1或2,则返回斐波那契数列的n-1和第n-2项
}
递归是函数设计时非常有用的小技巧,但是因为函数在内存当中使用的是栈区空间,递归调用过深可能导致栈溢出情况,导致函数还没实现完需要的功能就提前结束,这是需要程序编写者注意一点。
2.整形数组
1)方式一(图中arr1)不指定整形数组大小,用{value1,value2.......valuen}进行初始化
该方法声明的数组的大小等于n,即{.......}当中有多少个元素,整形数组的大小就为多少。
2)方式二(图中arr2)指定数组的大小
此时由于数组有了确定的大小,可以不初始化,如果该字符数组声明为全局变量,则数组当中的所有元素会被编译器自动初始化为0。但是声明在主函数或者其他函数当中则可能存放的是一些随机数,这取决于编译器本身的规则。
3)方式三(图中arr2)指定数组的大小,同时用{value1,value2.......valuen}进行初始化
除了{......}当中初始化值的部分,数组当中的其他值可能是随机值,也可能直接赋值为0,这取决于编译器本身的规则。
3.字符数组
1)方式一(图中str1)不指定字符数组大小,直接用一个字符串常量进行初始化。
该方法声明的字符数组的大小等于字符串常量大小+1,因为常量字符串后面要自动跟上一个'\0'。
2)方式二(图中str2)不指定字符数组大小,用{'字符1','字符2'.......'字符n'}的方式进行声明。
该方法声明的字符数组的大小等于n,即{.......}当中有多少字符,字符数组的大小就为多少。
小tips:在使用方式二声明的时候记得要在最后一个字符处加上'\0'!!以便让一些函数对字符数组当中存储的字符串进行操作,否则容易出现一些未定义操作。
3)方式三(图中str3)指定字符数组的大小。
此时由于字符数组有了确定的大小,可以不初始化,如果该字符数组声明为全局变量,则字符数组当中的所有元素会被编译器自动初始化为'\0'。但是声明在主函数或者其他函数当中则可能存放的是一些随机数,这取决于编译器本身的规则。
4.结构体
这部分内容在我的博客C语言当中结构体的两种声明方式以及结构体的无名声明方式当中有,这里不过多赘述。
八.总结
掌握以上知识,你的C语言基本已经入门了,已经可以试着使用C语言实现一些比较基本的算法,以及实现一些比较基础的功能,比如做一个计算机器,判断某一年是不是闰年,计算n的阶乘,计算斐波那契数列等。同时接下来你要学的内容就是C语言的精髓部分:泛型编程,指针,动态内存管理。C语言正是靠着它的精髓部分,在现在科技飞速发展的时代,保持着世界上最常用的编程语言之一的地位。
好了以上就是本文的全部内容了,如果这篇文章对你有帮助的话不妨给我点一个赞吧~~您的认可就是我最大的动力~~想要了解更多关于C语言的知识可以在CSDN/知乎上关注我,我是专注于C/C++的站长李蔚。感谢您的耐心观看,我们下篇博客再会!!