1、前言
指针,作为C/C++中最神秘、功能最强大的语法,着实是难以理解 、难以掌握、难以运用。😥
但是,能灵活的使用指针,对利用C/C++开发程序将有很大的帮助,让我们一起来了解了解吧。
2、啥是指针?
指针就是一个变量,一个存放着某个内存地址的变量,此变量需要开发者手动释放,否则会造成内存泄漏。作为程序猿,内存泄漏应该不陌生吧。
从上图可以看出,指针的本质就是一个变量,存放着某个变量的内存地址,
定义一个指针:
int a = 10;
int* point = &a;
- 定义指针:类型 * 变量名 = & 变量名;
- &为取址符(引用),获取某个变量的地址;
- *为解引用符,用于解出指针指向的内存地址。
- 任何类型的指针在32位处理器上是4个字节,在64为处理器上为8个字节。
3、指针的基本操作
3.1、常量指针
常量指针本质就是指向某个常量的指针。
指针的指向可以改变,但是指针指向的值不能改变,因为值是一个常量
#include <iostream>
using namespace std;
const int sum = 10;
const int number = 20;
int main() {
const int* p = ∑ // 定义一个指针指向一个常量
cout << *p << endl;
p = &number; // 改变指针的指向
cout << *p << endl;
// *p = 10; // 此处会报错,因为改变了常量的值。
return 0;
}
3.2、指针常量
指针常量本质是指针类型的常量。
指针的指向不可以改变,但是指针指向的值可以修改。
#include <iostream>
using namespace std;
int main() {
int a = 10, b = 20;
int* const point = &a;
cout << *point << endl; // 输出 10
//point = &b; // 报错, 不能改变指针常量的指向
*point = 30;
cout << *point << endl; // 输出 30
return 0;
}
在C/C++中有几个常见的指针常量:
- this指针,指向当前对象,通过它可以访问当前对象的所有成员。
- &变量(引用变量),可以改变变量的值,但是不能改变变量的指向。
- 数组名,数组名本质也是一个指针,指向内存的首地址,不能改变指向,但是可以改变每一个元素的值,
3.3、既是常量指针又是指针常量
既不能改变指针的指向,也不能改变指向指向的内存变量的值。
#include <iostream>
using namespace std;
int main() {
const int a = 10;
const int* const point = &a;
cout << *point << endl;
// 1、指向普通的变量
int b = 20;
point = &b; // 报错
// 2、指向其他常量
const int c = 30;
point = &c; // 报错
//3、改变指针指向的内存中的值
*point = 40; // 报错
return 0;
}
3.4、指针作为函数的返回值
语法:返回值类型* 函数名称(参数类型 参数值)
#include <iostream>
using namespace std;
int* function(int a, int b){
int* sum = new int;
*sum = a + b;
return sum;
}
int main() {
int* result = function(10, 20);
cout << *result << endl; // 输出 30
return 0;
}
当函数返回的类型是指针类型的时候,返回值就必须是在堆上开辟的内存空间,绝对不能返回函数中的局部变量,如果返回了局部变量就会报异常:
address of local variable ‘a’ returned [-Wreturn-local-addr]
那么为什么返回局部的指针变量不会报错?
不是不会报错,而是需要分情况而定,如果局部指针指向的是一个局部的普通变量,那么也同样会报错,因为指针的数据还是在栈上开辟,而栈上的数据在函数 执行完毕后就会被释放。
int* function(int a, int b){
int c = a + b;
int* sum = &c;
return sum; // 报错
}
而如果指针指向的数据是由new关键字开辟在堆上的数据,就不会报错。
int* function(int a, int b){
int* sum = new int;
*sum = a + b;
return sum;
}
3.5、指针做函数的形参
语法:返回值类型 方法名称(指针类型* 变量名称)
#include <iostream>
using namespace std;
int function(int* a, int* b){
int sum = (*a + *b);
return sum;
}
int main() {
int a = 10, b = 20;
int result = function(&a, &b);
cout << result << endl; // 输出 30
return 0;
}
只要是指针,然后符合语法规范,便可以变成常量指针、指针常量等。
而在发开中,我们也推荐使用指针作为函数的形参:
在C/C++中,我们可以使用sizeof关键字看到某个对象所占用的内存空间,当对象的属性非常多或者属性所占的内存比较多的时候,函数的形参也会复制实参的所有属性在内存中开辟相应的内存空间,那么此时就会十分的消耗内存。
上面我们了解到:任何类型的指针变量就只占四个或者八个字节,所以我们可以使用指针作为函数的形参,以此来减少内存的开销。
#include <iostream>
#include "headers/Person.hpp"
#include "string"
using namespace std;
int main() {
cout << sizeof(int *) << endl; // 输出 8
cout << sizeof(Person<string,string> *) << endl; // 输出 8
return 0;
}
因为我的电脑是64位的笔记本,所以任何指针类型的变量都只占8个字节。
4、引用类型(&引用指针)
引用指针本质就是一个指针常量,在有的书上也称为别名,是一个变量的副本,
引用的特点:
- 引用变量就是某个变量或者对象的别名
- 引用变量不占用内存
- 引用变量必须在声明时完成赋值,完成变量与引用的绑定
#include <iostream>
#include "headers/Person.hpp"
#include "string"
using namespace std;
int main() {
Person<string,string> person = Person<string ,string>("10001","abc"),
&point = person;
cout << sizeof(point) << endl; // 输出 64
cout << sizeof(person) << endl; // 输出 64
return 0;
}
不是说引用不占用内存空间吗?为什么引用和变量所占用的内存空间是一样的咧?
因为在表达式中,使用引用实际上就像使用变量本身一样,所以直接用sizeof是得不到引用本身的大小的。
但是我们可以查看地址,看引用的地址是否和变量的地址是同一个。
int main() {
Person<string,string> person = Person<string ,string>("10001","abc"),
&point = person;
cout << &person << "---->" << &point << endl;
return 0;
}
输出:
0xb00a9ff830---->0xb00a9ff830
可以看出是同一个地址。
4.1、引用类型作为函数的返回值
语法:返回值类型& 函数名(形参类型 形参名称)
好处:最大的好处是在内存中不产生返回值的副本
#include <iostream>
using namespace std;
int& function(int a, int b){
int sum = a + b;
return sum;
}
int main() {
cout << function(10, 20) << endl;
return 0;
}
此段程序报错,这里和用指针做函数的返回值是一样的错误,因为引用的本质还是指针,所以不能返回在栈上开辟的内存。
如果把function中的sum这个局部变量变为全局变量,那么这段程序就是正确的,或者使用new 在堆上开辟内存。
#include <iostream>
using namespace std;
int& function(int a, int b){
int* sum = new int;
*sum = (a + b);
return *sum;
}
int main() {
cout << function(10, 20) << endl; // 输出 30
return 0;
}
4.2、引用类型作为函数的形参
语法: 返回值类型 方法名(类型& 变量名)
#include <iostream>
using namespace std;
int function(int& a, int& b){
return a + b;
}
int main() {
int a = 10, b = 20;
cout << function(a, b) << endl; // 输出 30
return 0;
}
当我们使用引用类型作为函数的形参的时候,需要注意一个点:当我们修改了引用的值的时候,其实本质变量的值也会发生改变,因为引用只是一个别名,是会修改本质变量的值的。
#include <iostream>
#include "string"
using namespace std;
int function(int& a, int& b){
int sum = a + b;
a = 20;
b = 10;
return sum;
}
int main() {
int a = 10, b = 20;
cout << function(a, b) << endl; // 输出 30
cout << "a=" << a << ", b=" << b;
//输出a=20, b=10 此时a 和 b的值发生了交换。
//所以当别名修改了值之后,本质变量的值也会发生修改。
return 0;
}
5、总结
- 指针作为C/C++中的重点知识,还是要弄明白的好,不然大佬写的代码实在难以看懂。😰😨
- 指针的本质上也就是对内存的应用,但不是开辟内存,开辟内存还得使用calloc等函数,这两大点也就是这门语言令人着迷的地方,通过源码可以看到C/C++中大量的使用了指针,熟悉好这些内容这门语言也算是入了门了。
- 指针和引用本身都是一样的,只不过是引用相当于对指针进行了一层封装,知识点也差不多,只是多多少少存在一些坑,慢慢理解就行了。
- 以上是本人的个人观点,有不足之处还望指出🙂🙂🙂