栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

【一起学C++】指针

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【一起学C++】指针

目录

栈与自由存储区

使用指针操作自由存储区

指针操作栈内存

指针操作结构体、类

指针的判空操作

动态分配数组内存

常量指针

指针常量

常量指针 指向 指针常量

常量指针防止入参被修改

引用——隐式指针

常量引用

指针的引用

使用引用初始化指针

引用作为方法参数

引用作为函数的返回值


栈与自由存储区

在c++中的内存分为两个部分,一个是栈,一个是自由存储区。

栈就像一打扑克牌,当前执行的方法在扑克牌的最上面,如果用最上面的这张扑克牌表示当前的foo方法,那么这张扑克牌就是一个栈的一个切面,叫做栈帧。

1. 方法内部定义的参数,变量都在当前的栈帧中进行存储。当一个方法执行完毕,这个栈帧就会被删除,栈帧中的变量内存也就会被释放。

2. 当A方法调用B方法时,就会产生一个新的栈帧,放置在A方法的栈帧的上面一层。如果A方法调用B方法需要传递参数,实际上从A的栈帧中复制了一份到B的栈帧,B方法中无论怎么处理这些参数,都是不会影响到A。如果想要在B中改变A传递过来的参数,就需要特殊的处理。

自由存储区是独立于栈存在的内存区域,与栈中的任何一个栈帧的存储都没有关系。不足之处是,当你在自由存储区放置了变量,你需要手动去释放它的内存占用,否则会导致内存泄漏。当然,智能指针能够帮你解决这个问题。

小结:

1. c++的内存分为栈和自由存储区两个部分。

2. 栈的内存默认情况下控制在栈帧的级别,栈帧之间的存储不能够直接受到影响。

3. 方法内的参数,变量都存储在栈帧内,A方法调用B方法时,会复制一份到B的栈帧。

4. 自由存储区的存储空间独立于任何的栈帧,与栈内存没有关系。

5. 栈内存不需要手动释放内存,当栈帧退出时,内存自动释放。自由存储区的内存需要手动管理,需要手动去释放它的内存占用,否则会导致内存泄漏。智能指针能够帮你解决这个问题。

使用指针操作自由存储区

自由存储区支持我们放置任意的数据,但是需要我们手动的分配内存。例如我们需要放置一个整型变量,我们需要手动分配整型变量的内存,首先要定义一个指针:

int* intPtr;

这里的int* 表示我们要定义一个变量,这个变量指向一个整型数据的内存地址(引用)。

注:这里的引用,等价于内存地址。

指针可以认为是我们主动分配的整型数据内存空间的门牌号。

但是上面只是一个指针的声明,我们并没有实际赋值给它,它实际指向的地址是很随机的.

我们需要给他初始化。下面给它赋值了一个空指针,表示不指向任何位置。

int* intPtr {nullptr};

想要分配内存,需要使用new:

intPtr = new int;

接着我们需要对指针指向的内存地址中存储的内容进行修改。

*intPtr = 8;

现在,intPtr指向的自由存储区的内存中,存储的数值就是8.

注意,如果我们缺少了*,那么意义截然相反:

intPtr = 8;

 这里使用8对intPtr赋值,实际上是让指针指向新的存储位置8.

最后,使用完内存后,需要释放内存:

delete intPtr;

intPtr = nullptr;

指针操作栈内存

指针不仅可以操作自由存储区,还可以操作栈内存。

&可以取到栈内存中变量的实际内存地址,然后将指针指向这个地址。

int i {123};

int* intPtr {&i};

&i就是i变量在栈内存中的实际存储地址,我们把它赋给intPtr,此时intPtr指向了变量i的地址。

指针操作结构体、类

指针对结构体和类的操作稍显区别

Employee* anEmployee { getEmployee() };

cout << (*anEmployee).salary << endl;

这里首先调用getEmployee创建了一个Employee类的指针,然后使用(*pointer).property的方式调用指针指向的内存中存储的Employee对象的salary属性的数据。

这种调用稍显负杂,下面的方式是等价的:

Employee* anEmployee { getEmployee() };

cout << anEmployee->salary << endl;

这里直接使用 指针变量+ "->" 直接读取指针指向的内存中存储的Employee对象的salary属性的数据。

指针的判空操作

指针判空再使用是一个好的习惯,以下两种方式是等价的:

bool isValidSalary { (anEmployee && anEmployee->salary > 0) };

这里直接使用指针变量作为条件,如果anEmployee是nullptr的话,直接短路,不会执行后续的代码。

bool isValidSalary { (anEmployee != nullptr && anEmployee->salary > 0) };

这里使用了常规的!= nullptr的判断,效果是一样的。

动态分配数组内存

自由存储区也可以分配内存给数组,同样需要使用指针进行操作:

int* myVariableSizedArray { new int[8] };

此时在栈内存中分配了指针变量的存储空间,同时在自由存储区分配了8个整形数据的存储空间,并将指针指向第一个存储空间的内存地址 。

此时我们可以像使用数组一样使用数组指针了:

myVariableSizedArray[3] = 2;

最后在使用结束后不可忘记资源释放:

delete[] myVariableSizedArray;

myVariableSizedArray = nullptr;

常量指针

我们可以通过指针操作数组:

int* ptr;

ptr = new int[5];

ptr[3] = 1;

指针指向常量的意思是,指针所指向的内存地址的内容不允许发生改变,尤其是数组场景。

const int* ptr;

ptr = new int[5];

ptr[3] = 1; // 此处是无法编译的,因为ptr指向的数组是不可修改的

指针常量

指针常量指的是,这个指针一旦初始化不得重新更改其指向。

int* const ip { nullptr };

ip = new int[5]; // 无法编译,因为ip已经初始化,无法重新指向

ip[3] = 1; // 空指针

如果在初始化时,完成相关操作,内部数据的改变是允许的:

int* const ip {new int[5]};

ip[3] = 1; // 这是允许的,只是改变的数据,而没有改变指针的指向

常量指针 指向 指针常量
const int* const ip { new int[5] };

常量指针防止入参被修改

当我们避免方法修改我们的参数时,可以使用常量指针作为参数声明,避免对指针数据的修改:

void mysteryFunction(const string* someString)
{
    *someString = "Test"; // 无法编译
}

int main()
{
    string myString { "The string" };
    mysteryFunction(&myString);
}

对于基本数据类型也可以使用const,确保方法内没有赋值操作

void func(const int param) 
{ 
     
}

引用——隐式指针

引用的概念就是取到变量的地址,所有对引用的操作都会反映到变量本身,它是一种简化的隐式指针。

引用可以作为类的成员变量、方法的入参、方法的返回值等等。

引用变量的声明和初始化需要一起完成:

int i {8};

int& refI {i};

如果只是声明引用变量而不初始化,是无法编译的:

int& ref;

完成初始化以后,对引用变量的操作和对原始变量的操作是等价的:

i = 10;

refI = 10; // 等价

引用变量一旦完成初始化,其指向的地址不会发生改变,所有对引用变量的操作均提现到实际的变量上去:

int x { 3 }, y { 4 };

int& xRef { x };
xRef = y; // 此处xRef指向的x的值变为4,xRef引用变量本身没有发生变化

我们无法使用地址对引用变量赋值,因为取地址符获取到的是内存地址:

xRef = &y; // 无法编译

即便是两个引用变量相互赋值,也是实际指向数值的赋值:

int x { 3 }, z { 5 };
int& xRef { x };
int& zRef { z };

zRef = xRef; // z变成3

我们允许使用引用变量对引用变量进行初始化,实际也是指向相同的数据:

int x { 3 };
int& xRef { x };
int& zRef { xRef };

cout << zRef < 

常量引用

常量引用的含义是,某个引用指向的是一个常量,不可修改其值。

int z;
const int& zRef { z };    // 常量引用

zRef = 4; // 一旦赋值,无法修改,此处无法编译

与此同时,字面量无法用于初始化变量引用,但是可以初始化常量引用。

int& unnamedRef1 { 5 }; // 无法编译,常量无法初始化变量引用
const int& unnamedRef2 { 5 }; // 可以编译,常量可以初始化常量引用

指针的引用

定义一个指针:

int* ptr {nullptr};

指针的引用,指向指针:

int*& ref {ptr};

指针修改初始化:

ptr = new int;

通过引用修改指针指向的值:

*ref = 5;

使用引用初始化指针

定义变量

int i {5};

变量引用:

int& ref {i};

初始化指针:

int* ptr {&ref}; // 等价于int* ptr {&i};

指针赋值,修改数据:

*ptr = 123;

在这里,以下两个判断是满足条件的:

ptr == &ref

ptr == &i

引用作为方法参数

在c语言模式下的方法使用过程中,默认情况下方法是传递数据的副本到方法中,方法的实际指向中不会影响原始变量的值。

void swap(int a, int b)
{
    int temp = a;
    a = b;
    b = temp;
}

int main()
{
    // 对a和b没有影响
    int a = 5, b = 3;
    
    cout << a << "t" << b << endl;
    swap(a, b);
    cout << a << "t" << b << endl;

    return 0;
}

当我们存在某种需求,想要在方法中修改传递过去的变量值时,往往使用指针。

void swap(int* a, int* b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main()
{
    // 对a和b有影响
    int a = 5, b = 3;
    
	int* aPtr {&a};
	int* bPtr {&b};
    cout << a << "t" << b << endl;
    swap(aPtr, bPtr);
    cout << a << "t" << b << endl;

    return 0;
}

但是指针使用过于繁琐,所以使用引用来替换:

void swap(int& a, int& b)
{
    int temp = a;
    a = b;
    b = temp;
}

int main()
{
    // 对a和b有影响
    int a = 5, b = 3;
    cout << a << "t" << b << endl;
    swap(a, b);
    cout << a << "t" << b << endl;

    return 0;
}

常量引用作为方法参数有如下好处:

1. 无需复制一个新的变量给方法。

2. 无须担心方法会修改变量的值。

void printString(const string& myString)
{
    // 无法修改myString
    cout << myString << endl;
}

int main()
{
    string someString { "Hello World" };
    printString(someString);
    printString("Hello World"); // 也是允许的
}

引用作为函数的返回值

在操作符重载的时候,常使用引用作为方法的返回值。

1. 不要将方法内声明的引用作为返回值返回,因为方法终止时,引用会自动销毁。

2. 返回引用,是为了方法执行完成后继续使用这个引用。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/889887.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号