C基础1

基础回顾

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

  1. 若a没有进行初始化,且是局部变量有点忘记了或者不理解?没关系,下面会进行介绍),那么部分编译器会给a一个随机值,部分编译器会直接默认0,部分编译器会报错,所以大家最好定义变量时进行初始化,或者把它定义为全局变量

  2. 若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);
因为这里没有规定数组的长度,所以初始化时,里面有多少个字符,这个数组就有多长
但是实际上应该用一个函数strlen(s)去求

为什么要这样写?

首先,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[10000] = {0};
    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型
};

//格式
struct 结构体名 {

}; //要记得分号!
#include <stdio.h>

struct student {
	int age;
	double height;
};

int main()
{
	struct student a; //student是数据类型,表示学生这个结构体类型
		//后面的a是学生名,表示a这个学生;这就是上面所说的能被多个学生使用
	struct student b; //这样就又定义了一个b学生
	a.age = 15; //因为我们想把age,height这些数据保存在a这个学生集合中
		//a.age的意思就是a的age,把‘.’翻译成“的”
	a.height = 11.98; //a的height
	printf("%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中的语句,如果上面有满足了的,就不会执行else
    printf("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是1
		printf("1");
		break; //执行这段语句之后就break直接跳出switch这个大括号
	case 2:
		printf("2");
		break;
	case 10:
		printf("10");
		break;
	default: //如果没有一个符合,就执行default
		printf("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"); //少了break
	default: 
		printf("0");
		break;
	}
}

猜猜结果是什么

点击查看结果

结果是100

因为直接检索到的case 10这里,打印了10,但是没有break就不能结束,所以会接着执行default,打印0,直到碰到break,程序结束

posted @ 2025-03-04 23:14  PeachyGalaxy  阅读(127)  评论(0)    收藏  举报