系列介绍
本系列博客是博主自己的C语言学习笔记,分享出来即是为了整理学过的知识也希望帮助每一位零基础小白上手C语言。系列更新速度即为博主学习速度,如有错误疏漏,请务必及时指出!博主也会不定期的重新编辑所写。虽学无止境,望精益求精。
系列目录
知识讲解:
【C语言从青铜到王者】第零篇·与C语言来个约会
【C语言从青铜到王者】第一篇·详解分支与循环
实战演练:
【C语言王者实战训练营】第一期·修炼分支与循环
本篇前言
如果说编程是一场MOBA游戏,程序员是召唤师,那编程语言就是代替我们“参战”的英雄。C语言就是这样一个位于T0级别的热门英雄,常年位居TIOBE前三甲,是编程界当之无愧的“C位”。这篇文章,让我们先来初步认识一下它的每一个“技能”到底是什么,在今后的文章里再对这些技能的使用技巧做进一步的讲解。
博主的编程环境是Visual Studio 2019
被动技能学习阶段虽然不难,但是是我们今后学习主动技能必不可少的基础,请各位稳扎稳打,稍安勿躁
注释就是写在程序里但是不会执行的语句
注释掉暂时不需要的代码
在难懂的代码后面写一些注释充当“笔记”的作用,让下一个读代码的人能读懂
记笔记
…
下图是C++的注释风格
//
//I love C
//You love C
// We all love C
下图是C语言的注释风格
/* */
/*I love C
You love C
We all love C
*/
下面是一段程序
#include
int main()
{
printf("I love C\n");
return 0;
}
下图是程序编译的结果。
也就是说,我们敲了一段代码,然后程序在控制台给我们输出了“I love C”
也就是说我们一段程序实际上好像就只执行了这句话
printf("I love C\n");
再看这段程序
#include
int main()
{
printf("You love C\n");
return 0;
}
结果是
通过这样的“单一变量”的实验,我们大概可以知道了,除了“printf”开头的这句话以外的东西都是换汤不换药的
运用一下刚学的注释,就是:
#include
int main()
{
//在这里执行语句
return 0;
}
我们把上图除了注释以外的部分就叫做C语言的基本框架
我们来逐句讲解它们是什么意思
#include
这一行的作用是引出“头文件”
#include表示的是“引出头文件”,后面<>中的就是头文件的名字,这些名字的后缀一般是.h
头文件是什么?为什么要引出头文件呢?问题留着往下看
int main()
这一行的作用是定义主函数
int是函数返回值的类型,这里是整型
main是函数名,表示这是主函数
()是形式参数,主函数由于默认形式参数为void,void的意思是“无类型”,所以()里是空的
什么是函数返回值呢?什么是形式参数呢?具体内容在“主动技能——函数”中介绍,等不及的小伙伴可以直接走目录跳读
{
printf("I love C\n");
return 0;
}
{}一对大括号是一个整体,里面是主函数的内容
printf是一个函数,全称是print function,作用是把一些东西打印到屏幕上,用法是printf(“要打印的内容”)
但是这个函数我们自己并没有定义它是怎么运作的,它其实是我们“借来”的,像谁借的呢,就是像我们一开始定义的头文件stdio.h,头文件就是一个别人已经帮我们写好的函数库,stdio的全称是standard input output,表示一个涵盖输入输出类型函数的文件。我们引用它,计算机就可以调用别人已经写好的程序了
我们注意到I love C后面还有一个\n,它是转义字符,表示换行
return 0;表示这个函数的返回值是0
我们也注意到了在大括号内的每一行代码后面都有一个英文输入法的分号;它表示一行语句的结束,所以小伙伴们以后写语句千万不要忘记用“;”结束它哦
我们写程序的目的其实是通过借助计算机强大的计算能力来解决生活中的问题,那么我们必须得学会向计算机描述这个问题
比如我的身高是1.75m,体重是65kg,我的国家是China
这些数据各有家门,必须分门别类,它们各自的类型就是数据类型
char
short
int
long
long long
float
double
char-字符数据类型
用来表示字符
eg:ABCabc
short-短整型
int-整形
long-长整型
long long-更长的整形
整形都用来表示整数
eg:1234
float-单精度浮点数
double-双精度浮点数
浮点数表示小数,浮点的意思就是浮动的小数点
eg:1.75
介绍操作符sizeof(),它的作用是显示数据类型的大小(长度)
我们运行一下下面的程序看一下结果(简约起见结果注释在行末)
#include
int main()
{
printf("%d\n", sizeof(char));//1
printf("%d\n", sizeof(short));//2
printf("%d\n", sizeof(int));//4
printf("%d\n", sizeof(long));//4
printf("%d\n", sizeof(long long));//8
printf("%d\n", sizeof(float));//4
printf("%d\n", sizeof(double));//8
return 0;
}
这里的结果是数字,它的量纲是什么呢?答案就是“字节”,“字节”的英文名是byte
这里引入一下c语言中数据大小的单位
最小的单位是“比特位”,“比特”的英文名是bit
bit是一个二元量,在计算机语言中存放1或者0,表示计算机内部电路的“通”或者“断”,通过一连串的0/1我们就可以描述出一个整体的电路状态,这个电路状态就可以用来表示信息
我们可以把比特位想象成一个存放0或1的盒子,那么一个字节(byte)就是8个并排排放的盒子,可以同时存储8个0/1
以后我们说的数据的长度就是这一排存放0/1的盒子一共有多少个
大家看到上图有没有想到我们常见的8位二进制数,没错,实际上,一个byte就是可以存放一个8位的二进制数。一次类推,如果我们给一个数据分配了4字节的空间,那么就可以存放一个32位的二进制数,例如:
int a = 10;
我们定义了一个int类型的变量,变量名叫a,赋值为10,根据上面的知识我们知道int类型的变量长度是4字节
10 = 8 + 2 =1x23 + 0x22 + 1x21 + 0x20,那么,a在内存中的状态就是这样的:
上面的a是int类型,int类型的长度是四字节,也就是32个bit位,也就是说a这个变量有32个并排的盒子去装它的二进制序列。每个盒子中存放的1/0从左到右需要依次乘2的0次方、2的1次方、2的2次方…这是二进制数转换成十进制数的方法,是相当基础的知识,如果还有不懂的小伙伴可以自己做些功课哦
实际上,我们还有一系列的表示内存大小的单位
下面给出单位换算表
1 byte = 8 bit
1 kb = 1024 byte
1 mb = 1024 kb
1 gb = 1024 mb
1 tb = 1024 gb
1 pb = 1024 tb
(1024=210)
这里的kb、mb、gb等就是我们熟悉的U盘、网盘、硬盘、内存的容量单位,今天放到这里介绍,大家是不是对他们有更深的理解了呢
在程序中值可以继续变化的量叫做变量,值无法继续改变的量叫做常量
int age = 20;
float height = 1.74;
char ml = 'c';
定义变量时的写法是
数据类型 变量名 = 变量值 ;
也可以不给初值,但是我们在定义新变量的时候最好养成定义初值的习惯
给一个变量赋初值的这个行为还有一个帅气的名字——“初始化”
#include
int a = 2019;
int main()
{
int b = 2020;
{
int c = 2021;
printf("%d\n", c);
}
printf("%d\n%d\n", a, b);
}
a就是全局变量。可以看到a虽然在主函数外定义的但是可以在主函数里面使用,主函数的printf函数输出了a的值,也就是说a在全局都可以被使用。
b、c都是局部变量,可以发现b是在主函数的{}内被定义的,c是在主函数{}内部的{}内被定义的,那么b和c就只能在这个程序的局部被使用。
b和c都是局部变量,它们有区别吗?答案是有的
看下面的程序:
#include
int a = 2019;
int main()
{
int b = 2020;
{
int c = 2021;
printf("%d\n", b);
}
//printf("%d\n",c);
}
结果是
我们发现b在printf("%d\n",b);的大括号外部被定义,但是仍可以使用
如果我们把注释掉的printf("%d\n",c)加上,会发现程序错误,检测不到c:
#include
int a = 2019;
int main()
{
int b = 2020;
{
int c = 2021;
//printf("%d\n", b);
}
printf("%d\n", c);
}
总结:局部变量的作用域是自己所在的大括号内部,包括内部的大括号
C语言有四种常见的常量
#include
int main()
{
int a = 10;
printf("%d", a);
}
10就是字面常量
#include
int main()
{
const int a = 10;
a = 20;
printf("%d", a);
}
从上图可知const定义的变量不可变值,不可变值,就是具有常属性
#include
int main()
{
const int a = 10;
int b[a] = {
0 };
printf("%d", b[0]);
}
由于定义数组的大小必须是常量,从上图可知a的本质仍然是变量
这就解释了为什么const修饰的变量叫做常变量
#include
enum Sex
{
MALE,
FEMALE,
SECRET
};
int main()
{
enum Sex a = SECRET;
printf("%d", a);
return 0;
}
结果是2
我们解析一下:通过enum我们定义了一个“组”,名字是Sex,这个“组”实际上是一个常量组,组中的MALE,FEMALE,SECRET都是常量,它们的值分别是0,1,2,因为枚举常量未赋值时按序号赋默认值
在主函数中如果我们想要使用这样的常量,就先声明枚举组,就可以使用了:
enum 枚举组名称 变量名 = 枚举常量 ;
接下来看一下枚举常量的赋值问题,还是通过例子来看:
#include
enum Sex
{
MALE=3,
FEMALE=5,
SECRET=123
};
int main()
{
enum Sex a = SECRET;
printf("%d", a);
return 0;
}
结果是123,因为SECRET被赋值为123
#include
enum Sex
{
MALE=3,
FEMALE=5,
SECRET
};
int main()
{
enum Sex a = SECRET;
printf("%d", a);
return 0;
}
结果是6,因为SECRET上一个枚举常量FEMALE是5,所以按照序号,未赋值枚举常量SECRET是6
总结规律如下:
赋值枚举常量按所赋值定义
未赋值枚举常量按序号定义
转义就是意义被转变了,转义字符就是不是原本含义的,有特殊作用的字符组合
还是这个程序,\n的意思不是“斜杠和n”,而是换行的意思,\n就是转义字符
下面给出常见转义字符
一个基本要求是认识常见的转义字符,会数出含有转义字符的字符串的长度
补充知识:strlen函数
strlen–string length
用来计算字符串的长度
比如下面这段程序,我们来数一数字符串的长度吧
答案解析:
c : e s t 8 e s t . c共11个字符
两个\t是两个转义字符
一个\32是一个转义字符
一共14个字符
\0是一个转义字符,表示字符串的终止,计算字符串长度的时候作为结束的标识,不算做字符串的内容,没有长度
看这段程序
总结:
" "内字符串默认存在\0作为结束标志
’ ’ 内字符必须手动添加\0作为结束标志,否则会产生乱码,比如上图的arr2
用来处理变量、常量、字符和它们之间关系的符号
简单介绍:
比如1的二进制位是00000000000000000000000000000001一共32个数字
在总数字个数不变的情况下把这段数字向左移动一位,缺位补0
那么
00000000000000000000000000000001
就变成了
00000000000000000000000000000010
也就是二进制的数字2
写成代码的形式就是
#include
int main()
{
int a = 1;
int b = a << 1;
printf("%d", b);
return 0;
}
#include
int main()
{
int arr[] = {
0 };
int sz = sizeof(arr) / sizeof(arr[0]);
printf("%d", sz);
return 0;
}
#include
int main()
{
int a = 0;
printf("%d", ~a);
return 0;
}
输出的值是-1
为什么呢?
这里引入C语言中原码反码补码的概念
C语言中的二进制整数的第一位是符号位,0代表正数,1代表负数
- 正整数的原反补码相同
- 负整数的原码代表本身的值
设已知负整数的原码 则负整数的反码是原码符号位不动其他位按位取反(0变1,1变0)
补码是反码+1
接着看上面的程序,
a=0,也就是说a的原码是
00000000000000000000000000000000
那么~a就是
11111111111111111111111111111111
整数在内存中储存的是补码
也就是说打印出来的~a的补码是
11111111111111111111111111111111
那么~a的反码是(补码-1)
11111111111111111111111111111110
~a的原码是(反码除了符号位按位取反)
10000000000000000000000000000001
int a = 3.14;
输出的时候会报错,告诉我们数据类型不对的时候会丢失数据
那我们怎么才能强制让一个浮点数按照整形输出呢
int a = (int)3.14;
#include
int main()
{
int a = (int) 3.14;
printf("%d", a);
return 0;
}
结果是3
#include
int main()
{
int a = 0;
int b = ++a;
printf("%d\n", b);
int c = 0;
int d = c++;
printf("%d\n", d);
return 0;
}
++a——a先加一再参与b的赋值运算
c++——c先参与d的赋值运算再加一
上图结果是
1
0
双目操作符:操作两个对象的操作符
三目操作符:操作三个对象的操作符
…
测试是否大于 >
测试是否大于等于 >=
测试是否小于 <
测试是否小于等于 <=
测试是否不等于 !=
测试是否等于 ==
是结果为1,否结果为0
逗号隔开的一串表达式
#include
int main()
{
int a = 1;
int b = 2;
int c = 3;
int d = (a = b - 1, b = c + 2, c = a - b);
printf("%d", d);
return 0;
}
整个逗号表达式表示的并列语句,从左向右进行计算
整个表达式的结果是最后一句计算结束的结果
上图结果为-4
关键字是C语言自带的语法,不能自己创建关键字
在C语言中,关键字不能用作变量名
C语言中一共有32个常见关键字
给出32个关键字(表粗为今天介绍的4个关键字)
auto break case char
const continue default do
double else enum extern
float for goto if
int long register return
short signed sizeof static
struct switch typedef union
unsigned void volatile while
节选部分讲解,其余的之前已经讲过或今后会慢慢接触
- 什么是寄存器?
简单理解,寄存器就是运行速度很快,空间很小的储存数据的东西
它比内存快的多,所以使用寄存器可以大大的提高数据调用速度
所以,被大量频繁使用的数据,就可以放在寄存器变量中,提升效率
但是现在的许多编译器已经能够自动识别这种数据并把它们放进寄存器,所以register关键字现实使用意义不大
#include
typedef unsigned int u_int;
int main()
{
u_int d = -100;
printf("%d", d);
return 0;
}
typedef 旧类型名 新类型名 ;
#include
void Add()
{
static int a = 1;
a++;
printf("%d ", a);
}
int main()
{
int i = 0;
while (i < 10)
{
Add();
i++;
}
return 0;
}
第一次调用Add函数的时候,a=2
由于是static定义,a出作用域未被销毁,a仍然被定义且值为2
第二次调用Add函数的时候,a = a+1 = 2+1 = 3,以此类推
static修饰的局部变量,本质上是改变了变量的存储类型
把栈区的局部变量分配到了静态区
让局部变量的生命周期延长到了全局变量
static修饰全局变量
static修饰全局变量使得全局变量只能在自己的源文件中使用,不再能通过extern声明引用跨源文件的全局变量(忘记了回看全局变量中的“全局变量在相同解决方案不同源.c文件的使用”)
static修饰函数
static修饰函数使得函数只能在自己的源文件中被调用,不再能通过extern声明引用跨源文件的函数(与static修饰全局变量作用类似)
static修饰全局变量和函数,本质上是将它们的外连接属性变成了内连接属性,使得它们不再能在全局被引用或调用
注意:
define 、include等不是关键字,是预处理指令,以后会介绍,不要弄混
恭喜你已经习得C语言的各种被动技能,但是光有被动我们还只是一个任人宰割的小兵。接下来让我们开始学习C语言的主动技能吧,它们是C语言的重头戏,效果十分强劲,是我们以后征战沙场的得力助手。今后会继续就每一个主动技能做讲解,今天我们就先和它们打个照面,交个朋友吧!
我们在生活中常常会面对各种各样的选择,有时候会陷入两难的境地
那么,如何向计算机描述这种选择的情况呢?接下来我们引入第一个主动技能——选择语句
(由于是第零篇,仅介绍if-else语句)
if(条件)
{
条件为真的时候执行的语句;
}
else
{
条件为假的时候执行的语句;
}
看下面这段程序
#define _CRT_SECURE_NO_WARNINGS 1
#include
int main()
{
int a = 0;
printf("你想要一个女朋友吗\n");
scanf("%d", &a);
if (a)
{
printf("恭喜你脱单\n");
}
else
{
printf("单身万岁\n");
}
return 0;
}
程序的意思就是:
你想要女朋友吗
输入1,就会脱单
输入0,就继续单身
这样就是做出了一个选择
接下来让我们逐句解读一下
#define _CRT_SECURE_NO_WARNINGS 1
这句话是干啥的呢?我们暂时只需要知道这句话的作用是保证调用scanf函数的时候系统不会报错的就行了。如果你在运行程序的时候在出现scanf报错的情况,就请先加上它吧
由于讲过了程序框架,接下来我们就只看程序主体部分
int a = 0;
定义一个新的变量a并且初始化,初值为0
printf("你想要一个女朋友吗\n");
打印“你想要一个女朋友吗”这句话并且换行
scanf("%d", &a);
引入函数scanf
作用是把从键盘上输入的值赋值给指定变量
我们可以发现scanf函数和printf函数很像,唯一的不同就是在变量名前面必须加上一个取地址符&
这句话的意思从键盘接受一个数并且把它赋值给a
if (a)
{
printf("恭喜你脱单\n");
}
else
{
printf("单身万岁\n");
}
判断a的值,如果a的值是真(非零),就执行if下的语句:打印“恭喜你脱单”,如果a的值是零,就打印“单身万岁”
计算机最强大的地方就是可以把同一个步骤在极短的时间内不知疲倦的重复成千上万次,我们利用这个强大特性的语句就是循环语句。接下来引入第二个主动技能——循环语句
(由于是第零篇,仅介绍while语句)
while(条件)
{
条件为真时重复执行的语句;
}
条件为假则跳出循环
“编程之路”
#include
int main()
{
int input = 0;
printf("欢迎来到编程的世界\n");
printf("你要好好学习吗?(1/0):");
scanf("%d", &input);
if (input == 1)
{
printf("好工作\n");
int day = 0;
int line = 0;
printf("开始编程\n");
scanf("%d", &day);
while (line < day)
{
printf("坚持敲代码 坚持天数:%d\n", line);
line++;
}
if (line >= 20000)
printf("好offer\n");
else
printf("坠入爱河无心学习\n");
}
else
printf("回家种地\n");
return 0;
}
提示:使用了if的嵌套和while循环,并且使用if判断条件跳出循环
我们在初中就学过函数,比如这样一个函数
f1(x)=2x
它的意思是把对象变成自身的二倍,你可能觉得这个函数并不能起到什么简化的作用,再看这一个函数
f2(x,y)=(x3+y3)/(x3-y3)
如果我们每次使用都把后面这一串运算表示出来就太麻烦了,我们可以直接用f2(x,y)来表示
同理,c语言中我们可以用已经写好的函数来极大的帮助我们简化主函数,让我们的代码更加清晰,层次分明,可读性强
函数返回值类型 函数名(形式参数类型 形式参数名)
{
形式参数参与的语句;
return 返回值;
}
形式参数就就相当于f(x)=…中的x(实际参数就相当于使用函数时x的实际值)
返回值就是我们调用函数以后,函数输出的结果
“比大小”
#define _CRT_SECURE_NO_WARNINGS 1
#include
int Cpa(int a,int b)
{
if (a > = b)
{
return a;
}
else
{
return b;
}
}
int main()
{
int x = 0;
int y = 0;
int z = 0;
scanf("%d%d", &x, &y);
z = Cpa(x, y);
printf("%d", z);
return 0;
}
提示:注意定义函数的写法和调用函数的写法,注意形式参数和实际参数不要弄混
要想存储5个数,怎么存储?你可能觉得我定义5个变量abcde就好了。那50个数呢?500个数呢?
引出C语言中数组的概念:一组相同类型元素的集合
数组数据类型 数组名[数组元素个数] = {数组元素1,数组元素2};
数组名[下标]
#include
int main()
{
int i = 0;
int arr[10] = {
1 };
printf("%d", arr[0]);
return 0;
}
下标是按照0、1、2、3、4…的顺序排列的,所以第一个元素的下标是0
打印数组中所有的元素
#include
int main()
{
int arr[10] = {
1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", arr[0]);
int i = 0;
while (i < 10)
{
printf("%d\n", arr[i]);
i++;
}
return 0;
}
#defne 宏名(形式参数)(宏内容)
#include
#define Add(x,y) (x+y)
int main()
{
printf("%d", 5*Add(2, 3));
return 0;
}
上图结果为25
define定义宏本质就是替换,作用和函数类似
指针是C语言的灵魂,是当之无愧的大招。接下来我们详细讲解一下指针到底是个啥
内存是计算机中用来储存数据的空间
就像是一个个空房子
为了方便我们拜访和管理它们,每个房子都有自己的地址
内存是怎么编号的?
32位、64位——指的是32/64根地址线——物理线——通电——1/0——产生了电信号
电信号就能够存储数字信息
以32位的机器举例
二进制数字
从
00000000000000000000000000000000
到
11111111111111111111111111111111
一共232个数字
可以用来当作232个房间的地址
我们就把这232个房间的地址叫做地址
也就是说我们可以管理232个内存单元
最小的内存单元是多大空间?
假如最小的内存空间是bit
1GB=210MB=220KB=230Byte=233bit
也就是说以bit为最小单位的232个地址可以管理0.5GB的内存
这样的话管理的空间就太小了,不太合适
所以我们规定
最小的内存单元是字节(byte)
这样232个地址就可以管理4GB的内存
承接上面内存的内容,如果我们想要定义一个int型变量,将给它分配4个字节的空间
int main()
{
int a = 10;
int* pa = &a;
return 0;
}
*表示pa是指针变量,int说明pa执行的对象int类型的
再比如char类型的指针变量就是char *
int main()
{
char ch = 'a';
char* pa = &ch;
return 0;
}
int main()
{
int a = 10;
int* pa = &a;
*pa = 20;
return 0;
}
*解引用操作,*pa就是通过pa里面存放的地址找到a
*pa = 20;
这句话就是通过指针pa操作了指针指向的对象a,将a重新赋值为20
结构体可以通过不同的维度来描述复杂对象
创建结构体可以理解为创建了一个新的复合数据类型
struct 结构体名
{
定义成员变量1;
定义成员变量2;
};
struct Stu
{
char Name[20];
int age;
int height;
double score;
};
#include
struct Stu
{
char Name[20];
int age;
float height;
double score;
};
int main()
{
struct Stu s = {
"小白",18,1.74,85.5 };
printf("%s %d %f %lf", s.Name, s.age, s.height, s.score);
return 0;
}
struct Stu s = {
"小白",18,1.74,85.5 };
s是结构体变量名
{}内部是结构体各项的内容
printf("%s %d %f %lf", s.Name, s.age, s.height, s.score);
s.Name中的点操作符是用来访问结构体成员
结构体变量 . 结构体成员
#include
struct Stu
{
char Name[20];
int age;
float height;
double score;
};
int main()
{
struct Stu s = {
"小白",18,1.74,85.5 };
struct Stu* pa = &s;
printf("%s %d %f %lf", (*pa).Name, (*pa).age, (*pa).height, (*pa).score);
return 0;
}
pa就是结构体指针
但是这种写法有点啰嗦,看下面这种写法
#include
struct Stu
{
char Name[20];
int age;
float height;
double score;
};
int main()
{
struct Stu s = {
"小白",18,1.74,85.5 };
struct Stu* pa = &s;
printf("%s %d %f %lf", pa->Name, pa->age, pa->height, pa->score);
return 0;
}
->就是箭头操作符
语法是
结构体指针->结构体成员名
至此为止,初识C语言结束。我们已经不再是雾里看花的门外汉,真正走上了编程之路。但是这只是刚刚完成了定级赛,只是我们从青铜到王者的起点。由于是初识C语言的文章,许多知识实际上并没有形成良好的闭环,希望部分看官谅解。
好戏刚刚开始,诸位尽请期待…