用最简单的案例带你掌握C++中各种指针

1、前言

指针,作为C/C++中最神秘、功能最强大的语法,着实是难以理解 、难以掌握、难以运用。
但是,能灵活的使用指针,对利用C/C++开发程序将有很大的帮助,让我们一起来了解了解吧。

2、啥是指针?

指针就是一个变量,一个存放着某个内存地址的变量,此变量需要开发者手动释放,否则会造成内存泄漏。作为程序猿,内存泄漏应该不陌生吧。

在这里插入图片描述
从上图可以看出,指针的本质就是一个变量,存放着某个变量的内存地址,
定义一个指针:

int a = 10;
int* point = &a;
  1. 定义指针:类型 * 变量名 = & 变量名;
  2. &为取址符(引用),获取某个变量的地址;
  3. *为解引用符,用于解出指针指向的内存地址。
  4. 任何类型的指针在32位处理器上是4个字节,在64为处理器上为8个字节。

3、指针的基本操作

3.1、常量指针

常量指针本质就是指向某个常量的指针。
指针的指向可以改变,但是指针指向的值不能改变,因为值是一个常量

#include 
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 
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++中有几个常见的指针常量:

  1. this指针,指向当前对象,通过它可以访问当前对象的所有成员。
  2. &变量(引用变量),可以改变变量的值,但是不能改变变量的指向。
  3. 数组名,数组名本质也是一个指针,指向内存的首地址,不能改变指向,但是可以改变每一个元素的值,

3.3、既是常量指针又是指针常量

既不能改变指针的指向,也不能改变指向指向的内存变量的值。

#include 
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 
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 
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 
#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、引用类型(&引用指针)

引用指针本质就是一个指针常量,在有的书上也称为别名,是一个变量的副本,
引用的特点:

  1. 引用变量就是某个变量或者对象的别名
  2. 引用变量不占用内存
  3. 引用变量必须在声明时完成赋值,完成变量与引用的绑定
#include 
#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 
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 
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 
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 
#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、总结

  1. 指针作为C/C++中的重点知识,还是要弄明白的好,不然大佬写的代码实在难以看懂。
  2. 指针的本质上也就是对内存的应用,但不是开辟内存,开辟内存还得使用calloc等函数,这两大点也就是这门语言令人着迷的地方,通过源码可以看到C/C++中大量的使用了指针,熟悉好这些内容这门语言也算是入了门了。
  3. 指针和引用本身都是一样的,只不过是引用相当于对指针进行了一层封装,知识点也差不多,只是多多少少存在一些坑,慢慢理解就行了。
  4. 以上是本人的个人观点,有不足之处还望指出
    用最简单的案例带你掌握C++中各种指针_第1张图片

你可能感兴趣的:(C++,c++,c语言,开发语言,后端,github)