前言 👻作者:龟龟不断向前 👻简介:宁愿做一只不停跑的慢乌龟,也不想当一只三分钟热度的兔子。 👻专栏:C++初阶知识点
👻工具分享:
- 刷题: 牛客网 leetcode
- 笔记软件:有道云笔记
- 画图软件:Xmind(思维导图) diagrams(流程图)
如果觉得文章对你有帮助的话,还请点赞,关注,收藏支持博主🙊,如有不足还请指点,博主及时改正
初识C语言(三)
🚀1.函数
? 如果你是一个懒人,而你的main函数里面有大量的重复操作,比如大量的加法计算。如下
#include<stdio.h>
int main()
{
int a = 1, b = 2, c = 3, d = 4, e = 5, f = 6;
int sum1 = a + b;
int sum2 = c + d;
int sum3 = e + f;
printf("sum1 = %d\n", sum1);
printf("sum2 = %d\n", sum2);
printf("sum3 = %d\n", sum3);
return 0;
}
? 而且这仅仅是一个加法,如果以后是更加复杂的操作,而且要重复的进行很多次,这是懒人所不能接受的的?严重破坏了懒人主义,我马爹也不同意,这个时候我们可以设计一个函数,每次要进行这个操作的时候,调用这个函数帮我们完成即可。
🍉函数的用法
#include<stdio.h>
int Add(int x,int y)
{
return (x + y);
}
int main()
{
int a = 1, b = 2, c = 3, d = 4, e = 5, f = 6;
int sum1 = Add(a, b);
int sum2 = Add(c, d);
int sum3 = Add(e, f);
printf("sum1 = %d\n", sum1);
printf("sum2 = %d\n", sum2);
printf("sum3 = %d\n", sum3);
return 0;
}
? 函数的特点就是简化代码,代码复用。 目光要放长远,虽然这个例子我们还没有看到函数的太大作用,但是函数绝对是程序中必不可少的环节。
🍉函数的执行流程
? VS编译器,我们点击F10 (有些同学需要配合Fn 键使用)进入调试
? 目前程序已经执行到了int sum1 = Add(a,b) 这个语句,这个时候sum1 的值也没有计算出来,还是随机值。我们点击F11 进入函数内部!
? 当我们继续点击F11 ,出了函数,return (x+y) 就是将 x + y的值返回,而sum1 接受了这个返回值,从而将sum1 计算出来了。
🚀2.数组
顾名思义:一组相同类型元素的集合
问题:要存储1-10的数字,怎么存储?
虽然也可以如下暴力解决
#include<stdio.h>
int main()
{
int a1 = 1;
int a2 = 2;
return 0;
}
? 很明显上述的代码显得非常搓,给人的印象很不好,那么数组的概念就可以很好地解决上述问题
🍉数组的创建/数组元素访问
数组的定义/创建:类型 数组名 [元素个数]–int arr[10] 这里的元素个数必须是常量,整数
数组的元素访问:数组名 [下标],arr[6]注意下标是从0开始的,例如第7个元素的下标是第6和元素,这里的下标可以是变量,也可以是常量
注意:数组的定义和数组元素的访问都有[],但他们的作用是不同的
#include<stdio.h>
int main()
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
printf("%d\n", arr[6]);
int i = 0;
while (i < 10)
{
printf("%d ", arr[i]);
++i;
}
printf("\n");
return 0;
}
? 上述数组的初始化可以直接赋值,将一个集合(这个跟我们数学上的集合枚举法写法是已知的)赋值给数组。
?
? 当然了,上述数组创建的时候元素个数是可以不写的,编译器可以通过初始化时的元素个数,计算出来[]里面的值。
🚀3.操作符
🍉C操作符大全
? C语言之所以很灵活,一方面就取决于其丰富的操作符!
算术操作符
+ - * / %
移位操作符
>> <<
位操作符
& ^ |
赋值操作符
= += -= *= /= &= ^= |= >>= <<=
单目操作符
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
关系操作符
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”
逻辑操作符
&& 逻辑与
|| 逻辑或
条件操作符
exp1 ? exp2 : exp3
逗号表达式
exp1, exp2, exp3, …expN
下标引用、函数调用和结构成员
[] () . ->
? 当然这些操作符也不要求死记硬背,也不要求现在就要都会用,咱们以后的学习中都会学习到,用到的。
🍉常见操作符的用法介绍
🍇/–求商 %–求余数(算术操作符)
#include<stdio.h>
int main()
{
int m = 17;
int n = 4;
int q = m / n;
int r = m%n;
printf("%d\n", q);
printf("%d\n", r);
return 0;
}
扩展
这里给大家扩展一下,%操作符还是很常用的,经常会用到以下问题里面:
- 判断一个数是否能被另一个数整除–应用:用逢7,计算闰年
- 求两个数的最大公因数
- …………
🍇&&–并且 ||–或者(逻辑操作符)
exp1 && exp2 –这个表达式中,exp1 ,exp2 都为真,整个表达式才为真,否则为假
exp1 && exp2 –这个表达式中,exp1 ,exp2 只要有一个为真,整个表达式才为真,否则为假
计算机看不懂“真”,“假”,计算机判断条件真假是,0为假,非0为真
而我们传说中的if语句和while语句,if(条件),while(条件),他们他们的条件为真时,{}里面的语句才能被执行
&& 和 ||的理解有很多种
- 有些同学理解成both和either
- 有些同学理解成并且/都和或者
- 有些同学理解成电路中的串联和并联
大家用适合自己理解的去理解即可
举例1:
#include<stdio.h>
int main()
{
int a = 1;
int b = 2;
if (a && b)
{
printf("你们两都来啦\n");
}
else
{
printf("你们两怎么没都来");
}
return 0;
}
int main()
{
int a = 0;
int b = 0;
if (a || b)
{
printf("你们两至少有一个来了\n");
}
else
{
printf("你们两怎么没有一个人来\n");
}
return 0;
}
举例2:
int main()
{
int age = 0;
printf("请输入你的年龄\n");
scanf("%d", &age);
if (age >= 18 && age <= 36)
{
printf("你是青年\n");
}
else
{
printf("你不是青年\n");
}
return 0;
}
这里比较容易错,大家如果想要表示一个18-36的范围,千万不要像数学中一样直接来一手18 <= age <= 36 ,这是一个错误的逻辑,&&是一个二元操作符,一次只能进行两个操作数的操作,无法达到我们一次到位的效果
正确写法: if (age >= 18 && age <= 36)
🍇条件操作符exp1 ? exp2 : exp3
? 下面的图片可以大致解释条件表达式的意思
龟龟小故事:小明和小刚同时爱上了小红,小红也不知道怎么选择,于是绝对让小明和小刚做一个竞争,
小明赢了,那么小红就选择小明,小明输了(小刚赢了),小红就选择小刚
代码举例:
#include<stdio.h>
int main()
{
int a = 3;
int b = 10;
int c = a > b ? a : b;
printf("c = %d\n", c);
return 0;
}
上述代码中,如果 a > b,则c = a,否则 c = b
正经解释:
条件表达式也叫做三目表达式,因为操作数有三个
exp1?exp2:exp3 ,如果exp1 表达式的值为真,那么整个表达式的值就是exp2 的值,否则整个表达式的值就是exp3 的值
所以我们的求两数的最大值的函数可以稍微改善一下了:
改善前:
int Max(int x, int y)
{
if (x > y)
{
return x;
}
else
{
return y;
}
}
改善后:
int Max(int x, int y)
{
return x > y ? x : y;
}
龟龟小故事的结局与条件表达式无关😅
由于龟龟故事的结局有一点点大转变,审核警告我说我有点狂,所以大家可以去我仓库看龟龟小故事结局
🚀4.C关键字大全
? 我们按照类型,循环,分支对关键字进行一个分类
void
1.与类型有关的关键字
修饰类型 | auto | static | const | extern | sizeof | signed | typedef | unsigned |
---|
内置类型 | char | double | short | float | int | long | register | | 自定义类型 | union | struct | enum | | | | | |
2.与分支有关的关键字
case | default | else | goto | if | switch |
---|
3.与循环有关的关键字
剩下的关键字还有
void return volatile
? 这些关键字也是不需要死记硬背,后面我们都会用上
🚀5.scanf 的返回值
? 有些同学可能会有疑惑,scanf –不是scan format 不是格式化输入函数吗?怎么还有返回值
🍉成功输入
? 确实是有的,scanf 的返回值为成功输入的元素个数
#include<stdio.h>
int main()
{
int a = 0;
int b = 0;
int ret = scanf("%d %d", &a, &b);
printf("%d\n", ret);
return 0;
}
🍉输入失败
? 有成功输入的情况肯定也有输入失败的情况
? 上述操作我们只成功输入了一个数据,那么返回的是1
那要是全部输入失败了呢?
? 我们发现全部输入失败,scanf 会返回-1,而不是0。起始scanf 在全部输入失败时返回的时EOF ,而EOF 被做了一些操作
我们将鼠标放在EOF 上面,发现define EOF -1 ,EOF 时#define定义的标识符常量。EOF 的值本质就是-1
🍉解决OJ测试题多组测试用例输入
这里给大家推荐OJ刷题网站:牛客网,leetcode
这里我们看到的是多组输入,单组输入其实是无法解决该问题的
解决方法:
#include<stdio.h>
int main() {
int num = 0;
while(scanf("%d",&num) != EOF)
{
if(num >= 60)
{
printf("Pass\n");
}
else
{
printf("Fail\n");
}
}
return 0;
}
🚀6.关键字register的用法(如今作用不大)
🍉计算机存储金字塔层次结构
? 首先我们得介绍寄存器,高速缓存,内存,硬盘的区别
图解计算机存储金字塔层次结构:
? 图中的L1 ,L2 ,L3 就是高速缓存
平时我们所说的512GB就是电脑的硬盘,16GB叫做内存(手机上习惯叫运行内存)当然了我们现在还有所谓的网盘,网盘就在硬盘之下了。
一般的我们写代码向内存申请一块空间,然后创建一个变量,这个变量是存在内存中的,如果我们想提高效率,提高访问速度,可以在变量的类型前面,使用**register 修饰** ,这样就可以 建议编译器将变量放到寄存器存储,从而提高访问效率吧
#include<stdio.h>
int main()
{
register int a = 0;
return 0;
}
? register int a = 0; –当然了这只是对编译器的一种建议,现在编译器还是比较聪明的,**会自动将一些下面要用到的变量存在寄存器。**可以理解register 现在几乎作用不大。
🚀7.关键字typedef
功能就好像是:
就是取了个别名,但是并不是可以给任何东西取别名
typedef 顾名思义是类型定义,这里应该理解为类型重命名。
例如我们可以将一些复杂的类型名重命名以下
#include<stdio.h>
typedef unsigned int uint;
int main()
{
unsigned int a = 0;
uint b = 0;
return 0;
}
🚀8.关键字static
🍉static修饰的局部变量
题目测试:
#include<stdio.h>
void test()
{
int a = 0;
++a;
printf("%d ", a);
}
int main()
{
int i = 0;
while (i < 10)
{
test();
++i;
}
i = 0;
while (i < 10)
{
test();
++i;
}
printf("\n");
return 0;
}
? 同学们可以思考一下上述代码的输出结果是什么?
答案是是个1,你答对了吗?
1 1 1 1 1 1 1 1 1 1
原因解释:
? 因为我们讲过,局部变量的作用域是局部变量所在的局部范围–也叫代码块,当出了这个代码块,变量就会销毁,所以我们每次进入test函数,a都是重新创建的a,然后从0到1。
? 如果我们想要达到输出1~10的效果,就要让a的生命周期延长,使其出了代码块也不销毁,static就起作用了。
#include<stdio.h>
void test()
{
static int a = 0;
++a;
printf("%d ", a);
}
int main()
{
int i = 0;
while (i < 10)
{
test();
++i;
}
i = 0;
while (i < 10)
{
test();
++i;
}
printf("\n");
return 0;
}
? 这里还是稍微解释一下static 修饰的局部变量生命周期延长了。
🍉内存分区
? int a = 0; 这个局部变量a是放在栈里面的,出了代码块a就会销毁,而static int a = 0 这个a是放在静态区的,静态区的生命周期是从变量创建开始到程序结束,不归栈管,出了代码块a也没有销毁
🍉static修饰的全局变量
? 之前我们讲过全局变量的作用域是整个工程,具有外部链接属性,外部源文件也是可以使用当前源文件的全局变量的
? 如果我们将上述的global使用static修饰,那么global的外部连接属性也就消失了,也相当于作用域被缩小了
static int global = 100;
🍉static修饰函数
? 我们知道函数是具有外部链接属性的,而经过static修饰的函数,也会达到跟static修饰全局变量一样的效果,外部连接属性消失
🍉static的作用总结
1.static 修饰局部变量,使得变量的内存存储方式改变了,从栈区存到了静态区,生命周期被延长,出了局部范围也不销毁
2.static 修饰全局变量,使得变量的作用域缩小了,失去了外部链接数序,只能在本源文件中使用该变量
3.static 修饰函数,使得变量的作用域缩小了,失去了外部链接数序,只能在本源文件中使用该函数 4.static 可以影响生命周期,但是不会影响生命周期
? 全局变量和函数的下场就像,两个活泼的孩子,最近来疫情了,static妈妈管住他们,不让他们再往外面跑。居家隔离
🚀9.#define定义标识符和宏
🍉定义标识符
#define M 100
#include<stdio.h>
int main()
{
int a = M;
int arr[M];
printf("%d\n", a);
printf("%d\n", M);
return 0;
}
? 这个很简单咱们在讲常量的时候已经讲过了。
🍉定义宏
宏的用法跟函数的用法差不多,就是定义的时候有些许差别,咱们点到位置
#define ADD(x,y) (x+y)
#define MAX(x,y) (x>y?x:y)
#include<stdio.h>
int main()
{
printf("%d\n", ADD(3, 5)*8);
printf("%d\n", MAX(10, 20)*8);
return 0;
}
🚀10.C指针
? 想要清楚指针,我们进一步的了解内存
🍉内存
内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的 。 所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节。 为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址。
例如我们拿32位机器举例,x86–32位机器,拥有32根地址线,这些地址线一共有两种状态,通路与断路,1/0,根据数学知识
一共就存在00000000 00000000 00000000 00000000 ~ 11111111 11111111 11111111 11111111共2^32中状态
就有2^32次方个内存编号,由于二进制的繁琐型,我们通常使用十六进制来表示地址
思考:32位机器下的内存大小是多少?
答案是4GB,清楚内存单位的换算即可
? 我们也可以使用敲代码,讲变量的地址打印在屏幕上:
? 而且我们可以调开vs的内存窗口看看效果
有些同学可能会疑惑,为什么两次运行程序,a的地址会不一样呢?
同学们思考一下,你去一家酒店开房,每次去,老板给你开的房都是同一间吗?不太可能对吧因为其他房间可能被占用着
同样的,每次申请空间也并不是一直给你的是那块内存,可能那块内存其他程序正在使用着
🍉指针变量
? 那我们使用能使用变量将地址存储起来呢?答案是可以的,但是有两个问题:指针变量是什么类型?指针变量的大小是什么?
🍇指针变量的类型
#include<stdio.h>
int main()
{
int a = 10;
int* p = &a;
return 0;
}
? 如果指针变量存储的是一个int类型的地址,那么指针变量的类型就是int* ,同理我们可以推出如何存储double ,char 类型的数据
#include<stdio.h>
int main()
{
int a = 1;
double b = 2.2;
char c = 'w';
int* pa = &a;
double* pb = &b;
char* pc = &c;
return 0;
}
🍇指针变量的大小
? 是否还记得sizeof 操作符
int main()
{
int a = 1;
double b = 2.2;
char c = 'w';
int* pa = &a;
double* pb = &b;
char* pc = &c;
printf("%u\n", sizeof(pa));
printf("%u\n", sizeof(pb));
printf("%u\n", sizeof(pc));
printf("%u\n", sizeof(int*));
printf("%u\n", sizeof(double*));
printf("%u\n", sizeof(char*));
return 0;
}
答案都是:4(32位机器下)
%u – 打印32位机器下的无符号整形
%zd –打印64位机器下的无符号整形
这跟指针变量里面放的什么有关系,指针变量里面存放的是地址,地址是内存编号
32位机器下是32个bit的0/1数字,32位机器下是64个bit的0/1数字,根据 1byte = 8bit
所以32位机器下指针大小是4byte ,所以64位机器下指针大小是8byte
🍇指针的使用
同学们想一想,如果你知道了你朋友家的地址,你把这个地址记在你的小本本上,那么你是不是可以就可以通过这个小本本上面的地址找到你朋友家。如果朋友家没锁,是不是还可以干点其他事儿,当然这是现实生活中不允许的对吧
同样的,如果我得到了a的地址,那么我是否可以通过这个地址找到a,对a进行一些操作呢,答案是可以的
#include<stdio.h>
int main()
{
int a = 1;
double b = 2.2;
char c = 'w';
printf("a = %d\n", a);
printf("b = %.2f\n", b);
printf("c = %c\n", c);
int* pa = &a;
double* pb = &b;
char* pc = &c;
*pa = 8;
*pb = 4.4;
*pc = 'z';
printf("a = %d\n", a);
printf("b = %.2f\n", b);
printf("c = %c\n", c);
return 0;
}
🚀11.结构体struct
前面我们介绍数据类型的时候提到了内置类型–C语言提高给我们可以直接使用的类型
例如int ,double ,char ,生活中有一些数据我们使用内置类型无法定义,例如学生,汽车等
那么我们就要自己设计类型–自定义类型啦,开造
ps:我们可以自定义类型,但是不可以自己造一个关键字的哈
使用关键字struct
struct Stu
{
char name[20];
int age;
char sex[5];
char id[15];
};
如果我们自己定义的类型:
#include<stdio.h>
struct Stu
{
char name[20];
int age;
char sex[5];
char id[15];
};
int main()
{
struct Stu s = {"张三", 20, "男", "20180101"};
printf("name = %s age = %d sex = %s id = %s\n", s.name, s.age, s.sex, s.id);
struct Stu *ps = &s;
printf("name = %s age = %d sex = %s id = %s\n", ps->name, ps->age, ps->sex, ps-
>id);
return 0;
}
struct Stu 这个整体是一个类型名
在结构体变量右面加. 可以访问其成员变量
在结构体指针变量(存放结构体变量的地址)右面加-> 可以访问其成员变量
|