☘前言☘
今天是我开坑的第三天,大家最近应该都在忙期末把?答应我考完试来打卡好么0.0
今天依然是会介绍很多基础知识,但是会很实用,如果我有哪些没有讲清楚的,欢迎大家联系我,你提出的问题是我修改完善的基础,万分感谢。
- 欢迎大家加入我的打卡队列,如果你刷完了对你有帮助请你评论一个打卡。
- 如果你觉得这本书有用的话还希望多多支持作者。
如果觉得这个文章有用还希望大家交出素质三连呀。
🧑🏻作者简介:一个从工业设计改行学嵌入式的年轻人
✨联系方式:2201891280(QQ)
📔源码地址:https://gitee.com/xingleigao/algorithm-notes
⏳全文大约阅读时间: 80min
文章目录
🍭1. 基础知识点
有了上节课的一点点开头印象,我们这次就可以看看稍微更加难以理解的东东,这部分也是c语言经久不衰的灵魂——指针和函数。
那我们废话不多说,开始今天的学习吧。
🐌1.1 函数
如果程序逻辑比较复杂,代码量比较大、或者是重复性功能比较多,那么全写在main函数就会显得十分杂乱。为了使代码更简洁、思路更加清晰,C语言提供了函数功能。函数是一个实现一定功能的语句集合,并在需要时可以反复调用而不必每次重新写一遍。
其中我们用到的sin()
和cos()
都是已经定义好的函数方便我们调用。
🐰1.1.1 函数的定义
如果我们函数体内有大量的重复操作,我们就可以自己定义一个函数。
基本定义的方法为:返回类型 函数名称(参数类型 参数){ 函数主体 }
举个例子
#include<cstdio> void print1(){ printf("Haha,\n"); printf("Good idea!\n"); } int main(){ print1(); return 0; }
执行结果就是
Haha, Good idea!
我们看这个函数的类型,是void,所谓的void就是空,即不返回任何结果。
然后是我们调用的时候,由于不需要传参 所以括号里就是空的啥也没有,这种叫做无参函数。
再看一个例子
#include<cstdio> int judge(int x); int main(){ int a, ans; scanf("%d",&a); ans = judge(a); printf("%d\n",ans); return 0; } int judege(int x){ if(x > 0) return 1; else if(x == 0) return 0; else return -1; }
输入
-4
执行结果就是:-1
上面的代码就是将a传给x然后返回对应的值。
需要注意的点是
我这里故意把函数在后面定义了,如果不加第二行的那个声明编译器就会报错。所以如果你想写一个函数做占位符就要在最上面加声明。
如果你细心的话,会发现a传入怎么就变成了x了呢?
我们来看两个概念,就是全局变量和局部变量。
全局变量
全局变量就是定义之后所有程序段内都有效的变量。(可以理解为所有模块都能用)
举个例子#include<cstdio> int x; void chage(){ x = x + 1; } int main(){ x = 10; change(); printf("%d\n",x); return 0; }
输出结果
11
因为x是定义在最前面,每个函数都能找到这个变量的位置。所以就可以对它进行修改。
局部变量
相对的有一个概念就是局部变量,就是只有在函数体内部生效的变量。(可以理解为只在一小段程序内有效)
其实我们之前就接触到这个局部变量。for循环中的第一个初始化的时候如果声明变量,在for循环结束就会销毁。这是不是就是局部变量?
看个例子:#include<cstdio> void change(int x){ x = x + 1; } int main(){ int x = 10; change(x); printf("%d\n",x); return 0; }
执行的结果就是
10
为啥是10 内,其实因为change
内的x
和main
函数的x
并不是同一个x,change内的x只是main函数内x的一个副本。( 这句话你们可以以后再看)每个函数都有自己的调用栈,当调用一个函数的时候,会创建自己的内存空间,让后把对应的传入参数复制到自己的栈内存区。
如果希望传入的值发生改变应该怎么整呢?其实主要问题就是要让在一个新函数内找到原来的变量的位置。所以要传入一个地址。(超纲了。。。下面讲)
最后,传入参数可以有多个,如下面的例子。#include<cstdio> int MAX(int a, int b, int c){ int M; if(a >= b && a >= c) M = a; else if(b >= a && b >= c) M = b; else M = c; return M; } int main(){ int a, b, c; scanf("%d%d%d",&a, &b, &c); printf("%d\n",MAX(a, b, c); return 0; }
上面的程序就是打印三个值中最大的那个!
🐮1.1.2 main函数
我们一直在用main函数还没好好看看它233
无论一个程序多么复杂,程序执行都是先找名字叫做main的函数进行执行。
基本结果就是int main(){ return 0; }
其中的
return 0
就代表程序正常结束了。虽然现在main可以void。但是我不太建议,因为以后程序可能有一些问题返回不同的错误码非常有必要。
🐟1.1.3 数组作为函数传参
函数也是可以作为参数传入的。但是不需要写第一维的长度(本质还是传入了一个指针0.0)。
重要的是,数组作为传参时,在函数中对数组元素的修改就等于对原数组元素的修改。(毕竟指针都过来了,读到的元素也是之前的元素-.-)
示例:#include<cstdio> void change(int a[], int b[][5]){ a[0] = 1; a[1] = 3; a[2] = 5; b[0][0] = 1; } int main(){ int a[3] = {0}; int b[5][5] = {0}; change(a,b); for(int i = 0;i < 3; i++) printf("%d ",a[i]); return 0; }
上面的输出结果就是
1 3 5
,因为对数组的修改就是直接修改。(先记住吧。下面就会讲到,就是直接操作的原本元素)
🐬1.1.4 函数的递归调用
递归就是函数自己调用自己的过程。
#include<cstdio> int F(int n){ if(n == 0) return 1; else return F(n-1) * n; } int main(){ int n; scanf("%d",&n); printf("%d\n",F(n)); return 0; }
输入数据:
3
输出结果:6
我自信看完指针,这块你会有更深的理解0.0,先记住,等下解密。
⚔️1.2 指针
🗡1.2.1 指针究竟是什么
先放上一张神图。大名鼎鼎的冯·诺伊曼体系结构。
可以发现运算器能直接读取的程序是在存储器内的。那么运算器怎么拿到这个数据呢?
首先我们先明确,因为变量的读取都是从内存中的,所以一定会有一片空间在内存中。我们把它想象成有一栋公寓有一堆房间,我们想要找到我们的房间是不是得有个房间号?
然后有没有发现102 103 104 105
这四个房间我没画隔断,因为很多地方是按照门来编号的,但是一个大房间可能对应多个门呀。
- 上面的门的编号方式就是编制方式(可以一个门一个号,也可以两个门一个号),地址就每个门的房号,然后数据其实就是门内的内容。
- 我们根据房号找到家,计算机就是根据地址找到数据。
指针
其实就是地址。也就是上面的房号。
我们假如忘了门号就看看
外面门上的标识牌对吧?计算机内如果变量想要直到自己的地址也是需要看看自己的房号,怎么去看呢?用&
,因为是变量自己看所以就是&x
就拿到地址啦0.0
举个例子:#include<cstdio> int main(){ int a = 0; printf("%d %d\n", &a,a); return 0; }
输出结果可能是:
2686748 1
其中地址是一个unsigned类型的整数(64位是unsigned longlong),至于为啥是无符号的,你见过谁家房号是负数么0.0
🛡1.2.2 指针变量
指针变量用来存放地址,然后可以看下图,就是指针变量,比较厉害,手里有把钥匙,并且这个钥匙可以开对应的门。
然后由于这是一把钥匙,所以在声明它的时候需要在它前面加个*
,表示它是一把钥匙。也就是int *p;
,一般来说都是把*
放在变量前面。
同时*
也表示拿钥匙开门,*p
就表示拿到对应的数据,所以图上的就是104号房间对应的元素。
刚才知道&
是取地址,就是看看房间号,那么p = &a
就可以把p这把钥匙变成a所对应的地址。此时再去*p
就是a的值。
看下面的程序:#include<cstdio> int main(){ int a; int *p = &a; *p = 233; printf("%d %d", *p, a); return 0; }
输出结果是
233 233
,因为开房间的优先级很高,在赋值的时候是先打开门,再把数据写入,所以就把值写入到了a之中。最后输出就是两次a的值。
最后说明一下,不同的指针是不同的,比如下面的两把钥匙是不一样的。因为102-105是连着的,所以第一把钥匙钥匙+1实际地址会加4,而右边的指针对应的只有一个门,+1的时候只会加1,
其实对应的就是int *
和char *
类型的指针,其中一个长度为4,另一个为1。
这个指针的类型叫做基类型。
🔧1.2.3 指针与数组
之前有提到过数组就是一片连续的空间,数组名称也作为数组的首地址使用。
根据上面提到的指针加1等于加对应的数据长度,所以a+i
和&a[i]
是完全相同的。
在枚举元素的时候可以这么写:#include<cstdio> int main(){ int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; for(int *p = a; p < a +10;p+) printf("%d ",*p); return 0; }
指针的减法
#include<cstdio> int main(){ int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; int *p = a; int *q = &a[5]; printf("p = %d ", p); printf("q = %d ", q); printf("q - p = %d",q - p); }
输出结果是:
p = 2686688 q = 2686708 q - p = 5
会不会感觉震惊?这个q-p竟然不是20
???,其实指针的减法是计算两个指针直接差多少个基类型,因为是int所以返回的就是相差多少个int。
⚒1.2.4 使用指针变量作为函数参数
指针变量也可以作为函数的参数,这时会把变量的地址传入函数。如果在函数中对这个地址中的元素进行修改,那么原本的数据也会修改。
例如:#include<cstdio> void swap(int *a, int *b){ int temp = *a; *a = * b; *b = temp; } int main(){ int a = 1, b = 2; swap(&a,&b); printf("%d %d",a , b); return 0; }
结果就是
2 1
,是不是就做到了?但是为啥呢?我们来看看这个过程
可以发现在执行的过程中,swap直接改的就是原内存空间的值,当然改成功啦0.0
我们再看一个错误写法:
void swap(int *a,int *b){ int *temp = a; a = b; b = temp; }
这个如果看图就是讲swap调用栈里的a和b的值改了,并没有对之前的a和b造成任何影响。
🔨1.2.6 引用
引用是C++中一个强有力的写法,引用时候不产生副本,只是给原变量起了个别名。对引用变量的操作就是对原始变量的操作。
看一个例子:#include<cstdio> void change(int &b){ b = 1; } int main(){ int x = 10; change(x); printf("%d\n",x); return 0; }
上面打印的结果就是
1
。
其实底层实现的话就是使用的指针,我猜想应该是使用的不可修改指针进行实现的。感兴趣的话可以看看这篇博客。理解C++中引用的底层实现
但是要注意:
#include<cstdio> void swap(int &a, int &b){ int temp = a; a = b; b = temp; } int main(){ int a = 1, b = 2; swap(&a,&b); return 0; }
上面的写法会直接报错,因为a和b的地址都是常量,所有声明的变量的地址都是常量不可进行修改!!!所以引用的时候不能传入变量的取地址。
关于指针的一些知识点我写了一篇文章做总结,如果大家有兴趣的话可以去阅读一下,保证会有更深的理解0.0
关于室友收租懂了指针那点事
🐳课后习题
今天的题目难度也不高,也不老多 哈哈哈哈哈,我写完会放题解,大家写完了可以在评论区打卡哟!我觉得,题解我放评论区吧,这样不用修改文章。(大家有问题可以联系我,今天太晚了,题解等明天把)
题目 |
---|
2.6小节——C/C++快速入门->函数 |
2.7小节——C/C++快速入门->指针 |
题解:评论区见,置顶没有就是我没写完0.0,大佬们刷完打个卡