引用:
什么是引用?引用说白了就是给变量起别名,并且这个别名是从一而终的,只能进行一次引用!并且如果两个变量之前产生了引用关系,那么一个变量发生改变,与这个变量有引用或者间接引用关系的变量都会发生同样的改变!
引用实例:
#include <iostream>
using namespace std;
int main() {
int n = 2;
cout << n << endl;
int& r = n;
r = 3;
cout << n << ' ' << r << endl;
n = 5;
cout << n << ' ' << r << endl;
int& r1 = r;
int b = 6;
r1 = b;
cout << r1 << ' ' << r << ' ' << n << endl;
return 0;
}
分析引用实例:
这里我们设定一个 r 引用了 n ,那么也就意味着,r 和 n 是一回事了,这是因为 r 也指向了 n 所指向的内存地址,如果他们中的任何一个发生改变,r 和 n 就会同时发生改变!我们看到一开始 r = 3,这不是引用,而是赋值!因为两个原因:
引用的第一个原则:
引用只能引用一次,从一而终!如果再次发生 = 运算,就不是引用而是赋值!
引用的第二个原则:
引用只能引用变量,不能引用常量或者表达式!
索性加上引用的第三个原则:
引用在声明的时候必须同时初始化!!
继续分析实例:
我们发现无论改变了 n 还是 r,另一个的值也会同时发生改变!并且如果产生了引用的引用关系,比如:int& r1 = r2(其中r2也是一个引用),那么就说明 r1也指向了 r2所指向的变量,这下好了,三个兄弟就牢牢地绑在了一起!如果有一个兄弟发生变化,另一个兄弟就会同时发生变化!!
C的指针 & c++的引用 分析:
我们来看一个经典的实例:交换两个数字的值
先看一个C语言的典型错误案例:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void Swap(int a, int b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int n1, n2;
scanf("%d %d", &n1, &n2);
Swap(n1, n2);
printf("%d %d", n1, n2);
return 0;
}
代码执行的结果是:
这里很明显没有发生交换!
分析这个C语言典型错误案例:
首先我们要知道全局变量和局部变量,函数的参数及函数体内定义的变量都是局部变量,作用在局部作用域,一旦函数使用,则为这些局部变量开辟空间,一旦使用结束,就回收这些空间。
再来分析函数参数的传递方式:值传递,我们在函数外部定义并且赋值了的变量,可以通过值传递的方式,让这些变量变成实际参数,把值传给形参!注意的是,这里只是把值传给了形参,而并没有把地址传给形参!只要没把地址传给形参,就不会改变这个地址的值,所以我们可以把函数的执行理解为三部分:输入(在值传递中:就是传参的值输入),处理(就是函数体的代码实现功能),输出(就是函数的返回)。在值传递中,我们把数值传进去,做了一系列操作,但是只要没有更改调用函数处的变量地址的值,变量就不会发生实际的改变!所以我们为了得到正确的效果,不能采用值传递的方式!
正确的传递方式:指针传递,如果我们需要对传入的参数做地址的值的改变,就必须传入地址,以便修改地址的值。我们定义指针的过程如下:int* r = &n;
这个代码块的意思就是:确定一个整形的指针r,它指向变量n的地址。然后我们对r进行操作实际上就是对变量的地址的值进行操作。那么我们如何根据这些来设计合适的Swap()函数呢??
正确的C语言案例:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void Swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int n1, n2;
scanf("%d %d", &n1, &n2);
Swap(&n1, &n2);
printf("%d %d", n1, n2);
return 0;
}
代码的执行结果如下:
分析这个正确的C语言代码:
我们要传地址给函数,那么函数用什么参数来接收这个地址呢?我们之前的代码块说明了,是用指针来接收变量的地址的!所以我们函数的形参列表就要设定成指针,然后把变量的地址传给指针,就可在函数体内部对变量的地址的值进行操作了!
引用和指针的关系:
在c++里,我们把这种地址的指向关系,由原本的 ‘*’ ->指针,’&’ -> 地址,这种复杂的两个符号简化成一种表达关系:’&’ -> 引用,于是相对于的,就有了函数参数的引用传递。引用的代码模块:int& r = n;
这样就清晰的表达了引用!这启示了我们引用传参的传递方式:把变量传给一个引用即可!
C++的正确案例:
#include <iostream>
using namespace std;
void Swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int n1, n2;
cin >> n1 >> n2;
Swap(n1, n2);
cout << n1 << ' ' << n2;
return 0;
}
代码的效果就不做展示了!
总结:
总的来说,引用的对象和被引用的对象在产生引用关系之后,他们就变成了一回事,比如上面这个Swap()函数,我们把n1传给了引用a,n2传给了一个引用b,那么我们只要交换了a和b的值,由于他们的引用关系,n1 和n2 的值自然也就交换了!
const关键字的用法
常量指针:
特点:不可以通过常量指针去修改指针所指向的变量的地址的值。
注意:是不能通过这个指针去修改指向的内容,而不是内容本身不可以修改,如果修改了内容本身,那么这个指针所指向的内容自然就跟着被修改,实例如下:
#include <iostream>
using namespace std;
int main() {
int n1 = 2;
const int* p = &n1;
cout << n1 << ' ' << *p << endl;
n1 = 3;
cout << n1 << ' ' << *p << endl;
return 0;
}
执行结果如下:
另外注意的是:常量指针可以指向别的变量
比如上一个例子:改变常量指针指向的变量:p = &m;
这是可以的!
因为常量指针约束的仅仅只是不能通过常量指针去修改指针所指向的内容!
常量指针与非常量指针的赋值:
不可以把常量指针赋值给非常量指针,原因是:
假设我们让非常量指针指向了常量指针,这其实也就是把常量指针赋值给了非常量指针。由于非常量指针可以改变指向的内容,于是可以通过改变非常量指针,起到修改常量指针的作用,一旦修改了常量指针的内容,那么常量指针指向的变量是否也要跟着发生改变呢?自然就会改变!这样就与我们规定的,常量指针不得通过改变自身值来修改指针所指向的内容!
但是有没有别的办法去让常量指针赋予给非常量指针呢?当然有!因为常量化和非常量是两种不同的数据类型,所以我们可以通过强制类型转换去实现!实例如下:
#include <iostream>
using namespace std;
int main() {
int n = 2;
const int* p = &n;
int* r = (int*)p;
*r = 3;
cout << *r << ' ' << *p << ' ' << n << endl;
return 0;
}
我们可以把常量的类型:const int* 强制转换成非常量的:int*
常量指针作为函数参数:
可以用来保证函数的参数不会在函数体中被修改,进而导致指针所指向的内容被修改!
常量指针 & 指针常量:
我理解的、常量指针:
它的本质是指针,只是在指针意义下,它指向的对象是常量!这里我擅自使用了一个词:指针意义下,它的含义是:我们不可以通过指针去改变指向的对象。不能改变的值我们称之为常量。但是我们改变一个量的方法可以从值改变和指针改变两个角度去看,值改变我们是直接在变量的地址上修改了地址的值;而指针改变是改变了指针的地址的值,然后可能是通过某种映射关系然后改变了指向的内容。这个我是通过实验得出来的,所以我认为:常量指针,是让这个映射关系常量化,而不是让被映射的对象常量化!
我理解的、指针常量:
指针常量,是指指向的对象常量化,也就是说,我们设定的指针常量,是不可以把变量赋予给它的,比如下面这个实例:
#include <iostream>
using namespace std;
int main() {
int n = 2, m = 5;
int *const p = &n;
n = 3;
//p = &m;
*p = 4;
cout << *p << ' ' << n << endl;
return 0;
}
我们既可以修改指向的对象 n(可以把常量或者变量赋予给n),又可以修改指针的映射关系,也就是修改指针 *p,但是这个修改指针是有条件的!必须只能把常量赋予给指针,如果按照注释去做的话,会报错!
动态内存分配:
第一种用法:分配一个变量
格式:T* p = new T;
其中,T是一个任意类型名称,我们这句话实现了在内存堆区开辟了一个大小为:sizeof(T)
的空间,并且把这个空间的首地址给了p,让p成为了一个指针,并且成为了一个类型为T*的指针!然后我们可以通过对指针的操作,间接去修改操作分配空间的地址的值!
第二种用法:分配一个数组
格式:T* p = new T[MaxSize];
其中T也是一个任意的类型名称,MaxSize是一个常量、变量、表达式都可以!然后还是在堆区分配了大小为:MaxSize*(sizeof(T))的大小,并且把内存空间的首地址给p,这样分配数组空间是很好的,可以开很大的数组。坏处就是new是需要时间的,new的太频繁会降低程序的效率!
new的返回值类型:
这个很明显,返回类型就是上述的 T*,这样两边的类型才会匹配!
存储空间的回收释放:
格式:delete p;
其中,很重要的要求是,delete的对象,必须是由new创建的存储空间,不能是静态创建的!如果是delete数组对象呢?格式:delete[] p;
千万不能忘掉中括号,如果不写这个中括号,就不能完全释放动态创建的数组内存空间!