本文篇幅略长,我们的目标是大体了解C语言的基础知识,每个知识点都为最基本的通俗认识,之后会细讲。如有错误或理解不到位,还望各佬们大力批评斧正(右下角有目录可供查询)。若有帮助麻烦点个关注吧
C语言是什么
C语言与其他语言的对比
既然选择学习C语言,那我们要知道C语言是什么,相较于其他热门语言有何优势(java/cpp/pytho),由此目的我们先来简单了解一下为何学习C语言。
c语言诞生于1972年,是一个通用型命令式计算机编程语言,广泛用于底层开发,其支持结构化编程,词汇变量范围与递归,同时也是一种能够预防各类未预期操作的静态类型系统,最初的目标在于构建编写系统软件。
c++诞生于1983年,相较于C语言,c++紧随c语言的步伐,c++是C语言的超集,大家所知道的C语言是面向过程的。
Java语言诞生于1995年5月23日,并在1996年1月,第一个JDK-JDK1.0诞生,开启了一新编程语言的伟大逆袭之旅。java是面向对象的,那么C语言为了面向对象,所以诞生出现在大家所熟知的c++,被广泛视为大规模应用构建软件
python诞生于1989年的一个圣诞节,其创作者Guido van Rossum,Python第一个优势在于语法优雅简单,Python写起来就像写英文一样,可读性非常高,清晰易懂。比较容易理解。第二个优势是编程范式,它支持很多编程范式,面向过程、面向对象,它还支持函数式编程。
总的来说,C语言是一门基础语言,学好C语言更方便我们理解其他语言,做到C生万物
什么是面向对象,什么是面向过程
ok,大家看到这,不免会产生疑问,什么是面向对象?什么是面向过程?本作者给出以下通俗的解释
以平时大家在家中用洗衣机洗衣服为例子,彦祖们难免会产生以下几个动作:1.先将衣服一起放入洗衣机2.倒入洗衣服3.设置洗衣机工作模式,定时等系列操作4.启动洗衣机5.完成洗衣并甩干。当然更高端的洗衣机也会有更复杂的操作,而这些细枝末节的过程,彦祖们可以类比成我们语言中的面向过程
而我们的面向对象处理洗衣服的话,我们就可以抽象出几个工作需要的对象,如以洗衣服为例,我们就产生对象:彦祖们 衣服 洗衣机这3个对象。面向对象处理就不需要关心洗衣的过程,洗衣机是如何运作等等,我们只需要关心对象,而对象之间的交互我们就可以不用关心了
注意:面向对象和面向过程并没有好坏之分,以实际情况为重
什么是上层,什么是底层
这时候看了c语言中的对于广泛底层开发,你会问什么是上层什么是底层
如此图来看,我们电脑一般大致分为 硬件,操作系统,应用。而驱动层是操作系统通过驱动来控制硬件工作的,我们的操作系统及以下为底层,应用及以上为上层
如果有兴趣的话点我了解更多上层与底层
如何写一个C语言程序
相信哥,保姆级教学你驾驭不住
Hello World
俗话说的好,工欲善其事,必先利其器。我们想要写出C语言就要下载可以运行C语言的编辑器,我们以vs2022为例。
1.我们先点击创建新项目
2.依次点击空项目,并点击下一步
3.跳转到这一个界面时候,注意你的项目名称 最好写成英文的以免存放时发生乱码
4.点源文件-->添加-->新建
5.依次点击,这时候彦祖们会问了为什么是cpp后缀啊,因为vs里c和c++并不分家,值得注意的是你名称里要写上英文.c后缀
走到这一步我们就可以开始写代码了 其基本格式为
按ctrl+ F5或者ctrl+ fn+ F5运行我们的代码
C语言的基本格式
可能开始你并不知道这些都是什意思,你可以理解为这是写c语言的基本框架。接下来会一一解释
int main()与return 0;
我们先从程序第二行看起,你要知道main()是整个c语言程序的入口,即从此开始编译,{}这个的意思是你需要写的代码块就放在这里面, 而main前面的int为返回类型,现在不必深究下面会有解释,那么第6行的return 0;呢,顾名思义,return即返回的意思,看到这里你就想到了int(返回类型),是的!返回类型与返回值联动,return 0即返回一个0值。
printf(“Hello World”);与#include <stdio.h>
这里printf是你C编译系统提供的函数库中的输出语句,何为输出?即在你的屏幕上打印出内容,而后面的hello world为你输出内容,那么上面的#include <stdio.h>,在我们使用函数库里的输入输出函数时,编译器要求程序提供有关函数的信息,而#include <stdio.h>就是起提供信息的作用。通俗理解就是,若想用c语言函数库的东西(printf 等等)就要 先给人家打招呼,招呼都不打就直接拿来使用失败是必然的·。
#include
通俗理解为用此指令把后面所跟的信息调用,为编译预处理指令。
<stdio.h>
stdio.h是系统提供的一个文件名,stdio是standard input &output的缩写,顾名思义应该就可以知道为什么在调用printf语句时要加上这一句了吧。
注意:各位彦祖们在写代码的时候要使用英文输入法去写,vs是很严格的编译器
每一个语句后面要加入分号(;)表示这一行或者语句结束。养好良好的编译习惯从你我做起
一个基础的良好的习惯
学会写注释。
注释的作用是给代码做说明,方便别人阅读和理解代码,写注释是很好的编程习惯。编译器会直接忽略代码中的注释,注释不对程序功能产生任何影响,即使不写注释,也不影响程序代码。
三种注释写法:
1.单行注释,/* */之间的所有内容会被作为注释。
/* 这是注释 */
2.多行注释,/* */之间的多行内容会被作为注释。
/*
注释1
注释2
*/
3.单行注释
//这是一个注释
变量的命名
注意:你变量的命名最好要有一定的意义,不要abcd命到底,前期看不出什么弊端,到时候要写代码变多后连自己都不知道这个变量是什么意思了。
创建变量
各位大佬们创建变量的时候最好先给它赋一个0值,到最后再去赋予其他值不要直接这样创建
int num;
当然了彦祖们会疑惑这样创建有什么问题,接下来我们来一个报错解释
所以我们最后在初始化的时候先给赋予一个0值,到以后再赋予其他值,变量变量即以后可以被改变。不仅可以美化代码,也会降低报错的可能
int num=0;
变量的类型与大小
C语言中为什么需要变量类型?以你买菜为例,超市里的价格有零有整,买洋酒以英文开头等等实例证明C语言需要变量类型
有了各变量类型后,我们要知道各个变量类型所占的空间大小,这个时候我们需要我们的sizeof函数来计算,单位是字节
这个时候就要问了,什么是字节?我直接上图理解
bite--比特位,最小的计算机储存单位
byte--字节
kb
mb
gb
tb
pb
换算单位是8bite=1byte,其余的往下都是1024的换算
细心的朋友看见了,为什么我的long与int都是占4个字节
因为按照C语言规定sizeof(long)只需>=sizeof(int)即可
在C语言中,对于整型默认是int型,对于小数默认是double类型,编译器可以自动向上转型,如int转long,而double转float就不能自动转了,故在写float时后面值加f
而double就不用加了,默认小数是double类型
对于scanf的提议,
若想scanf不报错就需要在代码第一行添加
#define _CRT_SECURE_NO_WARNINGS 1
若想一劳永逸,就需要在newc++file.cpp里添加上面代码
作用域与生命周期
作用域(scope)
是程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用
的而限定这个名字的可用性的代码范围就是这个名字的作用域。
1. 局部变量的作用域是变量所在的局部范围。
2. 全局变量的作用域是整个工程。
通俗来说你的变量在哪你的此变量的作用域就在哪
生命周期
1. 局部变量的生命周期是:进入作用域生命周期开始,出作用域生命周期结束。
2. 全局变量的生命周期是:整个程序的生命周期
。
多说无益,各位可以尝试运行一下下面代码,就可以很清楚的知道代码的作用域与生命周期是说明意思了
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main() //简单来说变量在哪里可以使用哪里就是它的作用域
{
{
int a = 0;
printf("%d", a);
}
printf("%d", a);
}
常量
C语言中定义的常量分为以下几种
1.字面常量
2.枚举常量
3.const修饰的常量
4.#define定义的标识符常量
字面常量
字面常量:就是你平常的数字类型,如以下
1 整型常量
1.0 浮点数常量
a 字符常量
2.枚举常量
以下图代码为例,各位可以尝试一下编译枚举常量
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
enum Sex //enum是枚举常量的关键字定义在main函数外
{
MALE,
FEMALE,
SECRET,
};
//括号中的MALE,FEMALE,SECRET是枚举常量
int main()
{
printf("%d\n", MALE);
printf("%d\n", FEMALE);
printf("%d\n", SECRET);
//枚举常量依次从0开始往下递减
return 0;
}
3.const修饰的常量
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
const int a = 1;
a = 2; //这里会报错,即a不可修改
printf(“%d”,a);
return 0;
}
如上图可见const修饰的变量在语法上不可修改,而为什么说只是在语法上不能修改呢,因为其在本质上还是变量,彦祖请看
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
const int a = 1;
a = 2; //这里会报错,即a不可修改
int b[a] = {0}; 这说明了a本质还是变量
}
编译到这里的时候编译器就会告诉我们a要常量不能是变量,说明const只是在语法上不能被修改了
4.#define定义的标识符常量
#define定义的标识符常量是在main函数外面定义的,老规矩放入代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#define a 2
int main()
{
int b[a] = {0,1};
for (int i = 0; i < 2; i++)
{
printf("%d", b[i]); //这里说明了denfine本质上改变了变量->常量
}
return 0;
}
字符串与转义字符
1.字符串
字符串通俗来说就是你键盘上的东西都可以为字符,字符串就是多个字符。而在C语言中怎么定义单个字符与字符串呢
char a='a';
char arr[]="abcde";
简单来说,定义字符用单引号,定义字符串用双引号,用数组来装下
值得注意的是:字符串的结束标志是一个 \0 的转义字符。在计算字符串长度的时候 \0 是结束标志,不算作字符串内容。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main() //\0是字符串的停止标志,b只是多字符的数组,加上\0为字符串
{ //窗口监视里明显可以看见b最后没有0 F10-调试-窗口-监视
char a[]={ "abc" };
char b[] = { 'a','b','c' };
printf("%s\n", a);
printf("%s", b);
return 0; //简单看的出来\0才停止b数组只是多字符的数组加
}
感兴趣的彦祖们可以将上面代码实现,不难发现会出来以下内容
你会问了,为什么b数组会出来这么多乱码呢,那是因为b数组后面没有\0,本质是多个字符组成的数组,而不是字符串,你要打印b数组的字符串模式,电脑要找到\0才能停止,故打印abc后电脑会自动往下随机打印,直到遇到\0。
那怎么才能使b数组只打印出abc呢,各位可以思考下
ok解决了字符串的打印问题,我们来看看怎么肉眼证明\0是字符串的结束标志,还是老代码,通过监视窗口实现,F10-调试-窗口-监视,这是判断你代码的好工具。
2.转义字符
各位彦祖,这里我值写下我认为几个注意的地方
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
printf("c:\test\test.c\n");
printf("c:\\test\\test.c\n");
//说明了在写路径时使用\\以免转义为tab
return 0;
}
各位可以实现一下这些代码的输出,你就会发现有什么不一样了
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
printf("c:\test\test.c\n");
printf("c:\\test\\test.c\n");
//说明了在写路径时使用\\以免转义为tab
printf("%d", strlen("c:\test\test.c\n"));
//你切记转义字符都只算一个长度\n这样都算
return 0;
}
strlen是计算你字符串长度的工具,大家可以实现前想一想,c:\test\test.c\n一共是多少长度,聪明的你可能会想\t是空格的转义,那么我们就要加上空格的长度,\n是下一行的转义,这要怎么算呢。
可以看出来c:\test\test.c\n的长度是13。为什么是这个长度?我只能说
你切记,所有转义字符都看作一个长度
类似的
printf("%d", strlen("c:\test\628\test.c\n"));
//你看这里是15 \628你转8进制怎么转?只能算\62。
\ddd转8进制,读取1-3个数字,能读多就读多,这里的\628由于读不进去8(八进制里没有八)就只读取了62.
/与%的区别
/是求商的意思,%是取余的意思,我们用代码说话
int main() //15/2=7...1
{
printf("%d\n", 15 / 2); //你切记/是求商的值->7
printf("%d", 15 % 2); //%求余数->1
return 0;
}
看到这里,我们思考一下我们在求一个数能不能被5整除的时候应该用/5还是%5?
值得注意的是,在我们小%大时候是=小的
int main()
{
printf("%d", 2 % 3); //输出2
return 0;
}
%的一个有趣用法: 我们任何一个随机数%n,得到的结果一定是0-n之间的数,如我们的的一个随机数m%100,得到的结果肯定是在0-100(左闭右开)区间的,这个用法在后面的rand函数里经常实现
sizeof与strlen的区别
老规矩我们先看代码
int main()
{
int arr[] = { 1,2,3,4 };
printf("%d\n", sizeof(arr)); //16
printf("%d\n", sizeof(arr[0])); //4
//sizeof计算的是内存,即所占空间大小,单位是字节。arr为int型,int占4字节
char arr1[] = { "abcd" };
printf("%d\n", sizeof(arr1)); //5
printf("%d\n", strlen(arr1)); //4
//sizeof是计算字符串的长度,空间大小,不在乎你存的什么
//strlen是计算长度的,统计的是\0之前的
}
sizeof计算的是所占内存空间的大小,单位是字节,不在乎你内存里放的是什么(转义字符也算)
strlen是计算字符串的长度的,统计\0之前的字符个数
前置++与后置++的区别
int main()
{
int a = 10;
int b = 5;
b = a++; //后置的先使用再加加,先将10赋给b(使用),再++
printf("%d\n", a); //11
printf("%d\n", b); //10
a = 10;
b = 5;
b = ++a; //前置的先++后使用
printf("%d\n", a); //11
printf("%d\n", b); //11
return 0;
}
我们理解到前置与后置区别就在于先使用还是先++,理解到什么是使用后就可以完美类比出我们的--了
ok我们已经理解了前置,后置与作用域,我们直接来看这题
void test()
{
int a = 5;
a++;
printf("%d", a);
}
int main()
{
int i = 0;
while (i < 10)
{
test();
i++;
}
return 0;
}
秒懂的老铁建议去读博,不懂的建议去实现一下代码,做到知行合一
其实答案并不是你的打印6-15或者是打印10个5,为什么呢?
这考的是我们对作用域,生命周期的理解,局部变量出作用域就会销毁,所以test函数里的a并不能保留。
常见的关键字
我们看到关键字的时候,应该与我们的之前的变量命名联想
这里我们提几个前期不常见的关键字
auto
是自动变量,何为自动?通俗来讲,你在创建局部变量的时候,在作用域内,内存会自动创建 这个变量,出作用域时,内存会自动销毁,这就是自动变量,由于所有变量都是自动变量,故 隐藏了。
typedef
顾名思义就是类型定义,理解为类型重定义
重新定义unsigned int类型为uint的意思,意为着你uint新类型就可以代替Unsigned int了
unsigned
为无符号定义,意思是说你的变量不能带有符号如“-1”就是有符号
extern
声明来自外部的符号,可以调用本工程内外部(.c文件)的变量,函数等,只需在前面加上extern,是外部链接属性的桥梁,如下图
static
我们在了解static之前先看下static的英文解释(点击查看)
顾名思义static有静止的意思,它的作用和它的意思相近
static的作用有3个
1.修饰局部变量
2.修饰全局变量
3.修饰函数
修饰局部变量
void test()
{
static int a = 5;
a++;
printf("%d", a);
}
int main()
{
int i = 0;
while (i < 10)
{
test();
i++;
}
return 0;
}
这个代码咱们还有印象吧,就是局部变量自动销毁。当我们加入的static之后我们打印可以看见
ok我们在解释前我们要知道,我们的内存分为栈区,堆区,静态区
我们总结一下,static修饰局部变量,改变了变量的存储类型,本来局部变量是放在栈中的,但被修饰后就放在内存中的静态区了,因为存储类型的改变,从而导致了生命周期的变长, 但是作用域不受影响。给我们的感觉是延迟了生命周期。
修饰全局变量
由于2个.c文件不好复制代码,用图片表示(对着extern图)
我们加上了static后发现,test.c不能调用变量了。
我们总结一下,static修饰全局变量时,将全局变量的外部链接属性改变成了内部链接属性,此时的全局变量只能在自己的.c文件使用。给我们的感觉是:影响了作用域。
修饰函数
ok老铁们我们有之前的例子,我们直接看代码,注意函数是有外部链接属性的。
与之前的修饰全局变量其实类似
我们总结一下,static修饰函数时,将外部链接属性改变成了内部链接属性,使之只能在自己的.c文件使用。
分支和循环
分支
if
switch
循环
while
do while
for
goto
控制语句用于控制程序的执行流程,以实现程序的各种结构方式(C语言支持三种结构:顺序结构、选 择结构、循环结构),它们由特定的语句定义符组成,C语言有九种控制语句。
可分成以下三类: 1. 条件判断语句也叫分支语句:if语句、switch语句; 2. 循环执行语句:do while语句、while语句、for语句; 3. 转向语句:break语句、goto语句、continue语句、return语句。
if语句
if(表达式)
{
语句
}
if语句中表达式是真就执行语句(0为假非0为中真)
我们if语句中主要2点需要注意,一点是在if语句中使用判断式,赋值,等于。另一点是else悬停问题
我们先看第一点,当你表达式中为这样的情况
if(18<=age<30)
{
printf("青年");
}
ok,当我们的age=50的时候,你想我们程序不会执行整个语句,但事与愿违,电脑还是打印了青年,这是为什么呢?
因为这样的表达式从左往右依次判断,真为1假为0,程序进来,18小于等于age(50)吗?——真,为1——1小于30吗——真,为1。表达式中为1则执行下一步。
所以我们在使用这样的连续判断要加上我们的操作符(后面会详解操作符)
if(18 <= age && age < 30)
第一点中,我们还会遇到赋值和等于问题
==是等于
=是赋值
有时候我们的程序的分支语句得不到我们的结果,可能就是你把表达式中的等于变成了赋值,使其变为恒真了,为了避免整个问题,我们可以反写
if(5==a)
{
语句
}
如这样,当我们反写的时候,只写一个等号,编译器会报错的,如果你不反写,等于写成了赋值,编译器也不会报错。这也是一个好习惯
第二点,else悬停问题,你只要记住,else与其离的最近的匹配
大家可以思考一下下图
这里什么都不输出,因为else和最后一个if匹配
switch
switch(整型表达式)
{
语句项;
//是一些case语句:
//如下:
case 整形常量表达式:
语句;
}
值得注意的是,我们的char也算作整型表达式里面的一员,因为它返回的是ascll值,使用我们用int来接受字符是绰绰有余的。而case中的整形常量表达式是对值得判断
好习惯:我们在写switch语句时,记得在每个 switch 语句中都放一条default子句是个好习惯,甚至可以在后边再加一个 break 。
如果表达的值与所有的case标签的值都不匹配怎么办?其实也没什么,结果就是所有的语句都被跳过而已。程序并不会终止,也不会报错,因为这种情况在C中并不认为是个错误。
但是,如果你并不想忽略不匹配所有标签的表达式的值时该怎么办呢?你可以在语句列表中增加一条default子句,把下面的标签default:写在任何一个 case 标签可以出现的位置。
当 switch 表达式的值并不匹配所有 case 标签的值时,这个 default 子句后面的语句就会执行。所以,每个switch语句中只能出现一条default子句。但是它可以出现在语句列表的任何位置,而且语句流会像执行一个case标签一样执行default子句。
我们可以看下这题思考一下输出的是什么
循环语句
这里我们先讲一个for循环
for(表达式1; 表达式2; 表达式3)
{
循环语句;
}
表达式1 :为初始化部分,用于初始化循环变量的。
表达式2 :为条件判断部分,用于判断循环时候终止。
表达式3 :为调整部分,用于循环条件的调整。
循环语句中在后面会细讲,值得注意的是,continue与break,
break是跳出所在的循环中,不受if等语句限制
continue是跳过后面的语句,进入下一次循环
ok我们用前面的知识来一个测试
int main()
{
int i = 1;
while (i <= 10)
{
if (5 == i)
continue;
printf("%d ", i);
i++;
}
return 0;
}
这里是先输出1 2 3 4然后就陷入死循环了,不懂的可以去调试
调试是你最好的老师
初始指针
1.内存编号
在这里我们是初始指针,了解关于指针的相关的概念及来源
在此之前我们先来了解一下电脑内存
1.内存被划分一个一个的内存单元,一个单元选择1字节
2.内存单元进行了编号
内存,就是你电脑软件运行的地方,划分编号(类比我们的房间号)方便查找
下面以32位电脑位例子-32地址线-有32bit位
多少位的机器上有多少位的地址线,每一个地址以通电产生高低电频即1与0,这就是产生了我们说的内存,如32位机器上可以有2^32种地址信号可能,我们把这种地址信号可能当作我们的内存单元编号。
一个地址管理一个内存单元(地址编号,地址编号有2^32个),内存单元通过地址线产生,而一个内存单元是1个字节。所以在32位机器上我们有2^32个字节的内存大小,换算为4G. 64位就是2^64字节8G 地址是地址,内存是内存,我们只是用地址信号管理我们的内存单元,不要混为一谈
2.变量地址取法
int main()
{
int a = 0;
printf("%p", &a); //%p为地址类型
return 0;
}
如我们定义变量a=4,其实在内存里为a开辟了4个字节的内存大小(int类型占4字节),我们来看代码的运行结果
可以看但我们的地址是16进制表示的,老铁们会问了,不是每一个单元都有一个地址编号吗,为什么这里只显示了一个?
答:在C语言中我们如果开辟了是一块连续的空间,我们的地址是取其中最小的即起始地址作为整个 地址(为什么是这样的大家可以思考一下)
ok前面基础我们了解了这么多直接上指针
3.指针用法
在我们的C语言中,你切记地址就是指针,指针就是地址
指针的标识符就是*与&,如我们的int * pa=&a,int说明pa指向是int类型的,*表示你的pa是指针变量,指针变量指向地址,用&表示
我们看上图,指针变量pa指向了变量a的地址,我们要改变a的大小就可以通过pa来实现了,不过前面要加上我们的解引用操作符*
*pa=1; //代表这你a的值变为1了
4.指针变量内存大小
这是指针的基本操作,接下来我们来了解指针变量的内存大小
int main()
{
printf("%d\n", sizeof(char*));
printf("%d\n", sizeof(int*));
printf("%d\n", sizeof(long*));
printf("%d\n", sizeof(long long*));
printf("%d\n", sizeof(float*));
printf("%d\n", sizeof(double*));
return 0;
}
还是那句话
秒懂的老铁建议去读博,不懂的建议去实现一下代码,做到知行合一
我们来看代码的运行
咦?为什么我的指针变量全是4字节,为什么和我想的不一样?
让我们来回想一下前面的内容,指针就是地址,地址就是指针!,ok既然如此,我们的一个地址存储要多大空间,我们的指针变量就需要多大的空间。
X86(32位平台)——32地址线——32bit位——一个地址大小4byte——指针就是4字节
X64(64位平台)是多少大家可以去算算
各位老铁能看到这,本人表示极度的感谢。若有帮助麻烦点个关注吧。路漫漫其修远兮,望共勉。