基础回顾
Hello World到底是什么🤔
//预处理
#include <stdio.h> //include是找的意思,找到stdio这个头文件//.h是头文件的后缀,.c是c语言源文件的后缀,.cpp是c++源文件的后缀//std是standard标准,i是input输入,o是output输出
//程序的主入口
int main( ) //int表示main函数的返回值是整数,main是函数名,括号内是从外界传入的参数
{printf("Hello World"); //printf是stdio.h这个头文件中的库函数retrun 0; //可以不写,有些编译器会自动补全
}
Hello World 欢迎大家进入新的世界
现在大家是否能流畅地敲出Hello World呢,点击下面的链接试试
戳此处开始Hello World
数据类型怎么这么多😭
什么是占位符?
占位符
就是像 %d, %lf....这些,存在于输入和输出函数中,在输入和输出时会替换为一个特定的值
举个例子
//我想要输入输出一个整数int a; //定义一个整型变量a, 没有赋初始值就是没有进行初始化int a = 0;这样才叫进行了初始化
scanf("%d", &a); //键盘录入a的值(自己在键盘上敲一个数)
printf("%d", a); //在控制台上打印出双引号内的内容(注意此处是占位符,会被逗号后的变量值替换)
是否有点小疑惑
- 为啥 \(scanf\) 里面 \(a\) 的前面有 \(&\) 符号,可是 \(printf\) 中的 \(a\) 前面没有啊
- \(&\) 这个符号是什么意思啊
(现在不需要彻底理解,涉及到指针的内容,也可以记住就好)
&
它有三个名字,取地址符号 , 按位与 和 引用符号。在此处它是取地址符号,在c++中它才会拥有引用的身份
先说说上面的 变量a ,a代表的就是赋予它的值 :
赋值操作的本质是变量的内存地址中的内容发生了变化
一个程序是由一个个函数组成的,运行函数时需要为它分配内存,而定义变量时也需要为它分配内存,改变变量的本质 就是 改变变量的内存地址中的内容,而内存地址本身不会因为赋值操作而修改
(可以通过下面这段代码看看内存地址和地址中的内容的变化情况)
#include <stdio.h>int main()
{int a;printf("%p %d\n", &a, a); //%p是地址的占位符,输出a的地址和a的值a = 10;printf("%p %d\n", &a, a);
}
既然赋值是通过改变地址中的内容,那 \(a = 10\) 里根本没有出现取地址符号&,那它怎么进行修改啊
!说到这里,大家是不是理解了为什么scanf中是&a
因为我是想通过scanf进行键盘录入来修改a的值,而修改值是修改地址中的内容,一个孤零零的a代表的仅仅是一个常数值,所以我需要将scanf中录入的数据放到&a
即a的地址中,但是printf只是想让你输出a的值即可,所以就是输出a
-
若a没有进行初始化,且是局部变量(有点忘记了或者不理解?没关系,下面会进行介绍),那么部分编译器会给a一个随机值,部分编译器会直接默认0,部分编译器会报错,所以大家最好定义变量时进行初始化,或者把它定义为全局变量
-
若a进行了初始化,或者键盘录入了a,或者对a进行了赋值操作,那么此时a代表的就是我们最后赋予它的那个值
这就像数学中的参数x一样,题目给出x的值了,那么x代表的就是这个值
(这些知识就像一棵树一样,从一个枝丫到另一个枝丫,彼此之间相互连通,草蛇灰线)
全局变量
全局一般来说是函数外面
#include <stdio.h>
int a;int main()
{printf("%d", a);
}
由于它定义在函数外面,在你没有赋值的情况下,系统会默认赋值0,它的生命周期是整个程序,可以在任何一个定义的函数中使用
局部变量
局部一般来说是函数内部
#include <stdio.h>int main()
{int a = 10;printf("%d", a);
}
由于它定义在函数内部,所以它的生命周期是所在的这个函数,这个函数运行结束,它就会被销毁
要是a是一个全局变量,但是我在函数内部给它又赋了一个值,那它该听谁的啊
可以自己通过代码测试一下
#include <stdio.h>
int a = 10;int main()
{a = 20;printf("%d", a);
}
所以,当全局和局部同时出现时,优先考虑局部
说到全局和局部都出现的情况,有两个很典型的不同情况哦
const常变量
和define宏定义
- 常变量?一个变量让它拥有了常量的特性,简单来说就是一个值不能改变的变量,并且定义时一定要进行赋值
话不多说,自己用代码测试一下
#include <stdio.h>
const int a = 10; //const 数据类型 变量名 = 值;int main()
{a = 9; //有的编译器写到这里就会报错printf("%d", a);
}
#include <stdio.h>int main()
{const int a = 10;a = 9; printf("%d", a);
}
- define宏定义和const常变量的区别是,define只能在全局,且格式不同
写段代码测试一下
#include <stdio.h>
#define a 10 //末尾没有分号!!
//#define 变量名 值int main()
{a = 9; //有些编译器写到这就开始报错printf("%d", a);
}
通过一系列测试,我们知道了const常变量 和 define宏定义的变量值在之后不能进行修改!
(引入完毕,有没有思路变得清晰的感觉)
类型名 | 字节数 | 占位符 |
---|---|---|
short | 2 | %hd |
int | 4 | %d |
float | 4 | %f |
double | 8 | %lf |
long | 4 | %ld |
long long | 4 | %lld |
char | 1 | %c |
char [ ] | - | %s |
sizeof这个函数可以用来求字节数,可以试着求一下
//可以这样
int size1 = sizeof(int);//也可以这样
int a = 4;
int size2 = sizeof(a);
整数的类型有 short, int, long, long long
我们通常使用int, 1e9左右的数据都能用int表示
而超过1e9的数据我们会使用long long
小数的类型有 float, double
我们通常使用double, 尽量不要写出 long double的形式
字符的类型是char,这里的字符只能是符号,英文字母, 空格,不能是汉字,因为char只能是一个字节,而汉字有两个字节
字符串又称字符数组, 类型是 char s[ ]; s是字符串的名字
一个字符串可以是有一个字符构成,也可以由很多字符构成
变量的输入输出让人头大😴
换行
:在你需要换行的地方写上一个换行符\n
整型
之前我们提到,整型变量最常见的有2种 :int , long long
我们就以这两种为例
#include <stdio.h>int main()
{int n;scanf("%d", &n);printf("%d", n);
}
尝试一下long long 类型,和int差不多,要注意占位符不同
戳这里查看long long
#include <stdio.h>int main()
{long long n;scanf("%lld", &n);printf("%lld", n);
}
字符型
!在输入时一定要注意样例中不同字符可能用空格隔开
而不注意可能直接把空格输入进来,后面的正确字符没有输入进来
scanf里面的输入格式(比如间隔以及其它无用字符)要和样例中一样,才能正确输入
//下面是部分代码(不完整//我同时在一行输入了两个字符,它们中间用空格隔开,我想将它们分两行输出char a1,a2;
scanf("%c %c", &a1, &a2); //输入q w
//scanf里面必须有空格,必须和你输入的格式一模一样不然a2这个字符接收的就是空格
printf("%c\n%c", a1, a2);
来试试这道题吧
https://www.luogu.com.cn/problem/B2005
字符串型
字符串在c语言中都是用字符数组表示的,而字符数组表示字符串就有一个特点,就是字符数组都以\0
结尾。
给你一个字符串s = "abcd",用字符数组表示,求一下它有多少个字符
有5个
实际上它在数组中是这么表示的char s[ ] = {'a', 'b', 'c', 'd', '\0'};
可以写一行代码去求 int size = sizeof(s) / sizeof(char);
为什么要这样写?
首先,sizeof求的是字节数,而我们这里要求的是字符数,如何利用sizeof求呢,总的字节数 / 单位字节数 = 字符数,总字节数就是sizeof(s); 单位字节数就是这是个什么类型,sizeof(char)
注意
如果在初始化字符数组s时写的是char s[10];
那么利用sizeof计算的字符数只会是10,你已经将大小确定了,即使char s[10] = "abcd"; 但是会在后面给你自动补0,一直到数组的长度达到10为止
如何以字符串的形式,输入输出“abcd”这个字符串
有两种读取方式
- 字符串长度已经固定,一个一个字符输入(假设为4)
//由于最后有一个'\0',所以开数组的时候长度要加一 char t[5]; scanf("%c", &t[0]); scanf("%c", &t[1]); scanf("%c", &t[2]); scanf("%c", &t[3]); printf("%s", t);
- 直接以一个字符串的形式输入
char t[]; scanf("%s", t); printf("%s", t);
解释
-
字符数组默认都是从0下标开始,如果你不想用0这个下标,可以向第一种方式一样,指定字符的位置进行输入
-
为什么第二种的scanf里面没有
&
?
(此部分会在数组与指针的章节讲到,现在记住就好)
由于scanf是想将这个东西放到我指定的变量的内存地址中,一般的变量都是用&
来表示内存地址,但是数组(这里是字符数组)不一样!数组的变量名就是数组的地址,所以就不用取地址
假如我只有一个字符需要输入,或者我不想在scanf中保持和样例一样的格式,我该怎么做?
现在就可以用到一个有时候很方便的两个函数,getchar()
和putchar()
- getchar( ) 是从键盘录入中接收一个字符,你可以选择要这个字符
char q = getchar(); //是不是比scanf("%c", &q);写起来方便一点
printf("%c", q);
也可以选择不要这个字符,有可能碰到我们需要的字符之间,它多出了空格或者换行符\n
,就可用用getchar()接收之后丢掉
//分两行输入
//a
//b
char q, w; //定义一下这两个字符变量
scanf("%c", &q);
getchar(); //接收换行符,如果直接跳到下一步的话,那么w接收的就是换行符,因为换行符也是一个字符
scanf("%c", &w);
- putchar()是输出一个字符,把想要输出的字符放在( )里面
char q = 'a';
putchar(q); //输出q这个字符变量
putchar('a'); //输出a这个字符,只有变量不用单引号,其它的字符需要单引号,字符串需要双引号
结构体看起来好复杂啊🙁
结构体通常定义在函数外面,即全局,这样就能被所有函数使用
结构体方便之处在于它就是一个集合,比如 学生 这个集合中,包含各种类型的变量,就像身高,体重,年龄,邮箱,地址....并且定义好这个结构体之后,可以被各个学生使用(后面用例子解释)
通过结构体,学生A的所有特性能够方便地进行输入输出,并打包保存在这个结构体中
举个例子
struct student { //定义了一个student型的结构体int age; //年龄,int型double height; //身高,double型char email[]; //邮箱,字符串型
};//格式
struct 结构体名 {}; //要记得分号!
#include <stdio.h>struct student {int age;double height;char email[];
};int main()
{student a; //student是数据类型,表示学生这个结构体类型//后面的a是学生名,表示a这个学生;这就是上面所说的能被多个学生使用student b; //这样就又定义了一个b学生a.age = 15; //因为我们想把age,height这些数据保存在a这个学生集合中//a.age的意思就是a的age,把‘.’翻译成“的”a.height = 11.98; //a的heightprintf("%d", a.age);
}
算术 && 逻辑 表达式有点高级😊
算术表达式
大部分和数学中的一样,*
表示乘
,/
表示除(如果是整数除以整数就是向下取整),%
表示取模,<=
表示小于等于 , ++a
表示a先自增1再进行运算,a++
表示先将这一整行代码执行完,a再自增;\(a += b\) 就是相加的结果赋给前面的变量a等价于 \(a = a + b\),同理 \(a -= b\) 等价于 \(a = a - b\) , \(a *= b\) 等价于 \(a = a * b\) , \(a /= b\) 等价于 \(a = a / b\)
比如
#include <stdio.h>int main()
{int a = 10, b = 0, u = 0;u = a++, b += 2;printf("u = %d, a = %d", u, a); //猜猜最后输出的是什么,这个地方有点小坑
//将代码实现一下,看和猜的是否一样
}
改了一下下,结果会不同吗
#include <stdio.h>int main()
{int a = 10, b = 0, u = 0;u = ++a, b += 2; //这一行不同printf("u = %d, a = %d", u, a);
}
if语句中括号里面有很多判断运算符,它们是关系运算符,通常我们将成立
即if能够运行用正数来表示,不成立
用非正数表示,0或者负数
[具体的在if里面会讲]
逻辑表达式
&& | 与 | &&两侧同时成立(即都为正数)才成立 |
---|---|---|
II | 或 | II两侧只要有一侧成立就成立 |
! | 非 | 就是取反的意思,如果这个原本不成立,取反之后就成立 |
最易错的就是有的逻辑运算符具有短路效应
,这会让我们的计算方便很多
比如0 && 100
这个表达式看到前面有个0就已经不成立了,不用管100,只要有一个不成立,这个表达式就不成立
又比如1 || 0
这个表达式看到前面有个1就已经成立了,不用管后面的0,因为在或的表达式里,只要有一个成立就成立了
#include <stdio.h>int main()
{int i = 0, a = 0, b = 2, c = 3, d = 4;i = a++ && ++b && d++;printf("a = %d\n b = %d\n c = %d\n", a, b, c, d);//猜猜最后输出的是什么
}
查看答案和解释
a = 1
b = 2
c = 3
因为i = a++ && ++b && d++这个表达式,在进行第一步a++的时候,此时a没有自增,还是0,已经不成立了,所以后面的都不会进行,所以只有a在执行完之后能自增
一步步执行,执行到a++发现不成立了,就是短路效应
一道有点小坑的经典 A + B 问题
戳此处查看题目
一道有关基本运算以及输出的问题
继续戳
有两个小坑的题目
https://www.acwing.com/problem/content/620/
终于到流程控制语句了😀
if && else if && else
用例子来解释吧
int a = 10;
if(a < 5) { //如果满足a < 5,那么这个表达式就成立,等价于1或者任何正数,这个if语句才能进行;否则根本不会执行大括号内的内容printf("1");
}
else if(a < 10 && a >= 5) { //如果既满足a<10又满足a >= 5这个表达式才成立,才能执行大括号内的内容//注意!!不能写连等,就像5<=a<10是不行的,只能分开写printf("2");
}
else { //如果上面的if或者else if都不满足才会执行else中的语句,如果上面有满足了的,就不会执行elseprintf("3");
}
if结构是从上往下依次执行,有一个执行了,那么下面的else if或者else都不会执行
if语句可以只有if,也可以既有if 又有很多个else if(又很多种情况),也可以有else
!!if和else都只能有一个
有一个小原则else与最近的if配对
,有时候我们会将大括号省略,为例方便,这样不容易知道if该和那个else配对
switch选择语句
和if语句差不多,适合选择比较少的时候,逐个列举选项;缺点是不能选择范围,只能是字符或整数
#include <stdio.h>int main()
{int a = 10;switch (a) { //计算结果只能是整数或字符case 1: //如果a是1printf("1");break; //执行这段语句之后就break直接跳出switch这个大括号case 2:printf("2");break;case 10:printf("10");break;default: //如果没有一个符合,就执行defaultprintf("0");break;}
}
注意
-
if一般是对一个范围进行判断,从上往下依次执行
-
switch不是从上往下依次检索,就是直接匹配,更高效
-
case穿透,如果一个case里面没有break的话,那么执行完这个语句后就不会结束,会一直向下执行,直到碰到break才会停止
#include <stdio.h>int main()
{int a = 10;switch (a) { case 1: printf("1");break; case 2:printf("2");break;case 10:printf("10"); //少了breakdefault: printf("0");break;}
}
猜猜结果是什么
点击查看结果
结果是100
因为直接检索到的case 10这里,打印了10,但是没有break就不能结束,所以会接着执行default,打印0,直到碰到break,程序结束