欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C++入门教程之内存和指针

程序员文章站 2022-12-11 09:48:47
内存和内存地址 内存就是电脑里的内存条,内存大小就是内存条的容量。操作系统和程序在运行过程中都需要用到不同的数据,而运行过程中用到的数据都保存在内存中。当操作系统结束时,也就是关...

内存和内存地址

内存就是电脑里的内存条,内存大小就是内存条的容量。操作系统和程序在运行过程中都需要用到不同的数据,而运行过程中用到的数据都保存在内存中。当操作系统结束时,也就是关机的时候,因为内存条已经不通电了,所以内存里的数据都会清空。如果想把数据永久保存,那么你需要的就是保存到硬盘,就是因为硬盘的构造使它断电都不会丢失数据。内存的读写比硬盘快很多。

程序需要内存时,就会向操作系统申请,操作系统分配内存然后把内存的地址返回给程序,那么程序就可以通过内存地址向内存存放数据。当程序结束时,操作系统就会回收程序占用的所有内存。

内存是逐个字节进行划分的,然后操作系统为每个字节的内存按顺序给一个编号,而这个编号就是内存地址。那么操作系统和应用程序就可以通过内存地址快速找到这段内存,然后对内存中的数据进行操作。

在C++中,用变量来简化表示内存,这样你就可以通过变量很简单地操控这份内存了。平时你对变量的操作就是对这份内存的操作。

操作系统是从内存条剩下的可用内存中给程序分配内存的。操作系统为程序分配两种内存:一种叫做栈内存,操作系统给程序分的配栈内存是有上限的,而且非常少;另一种叫堆内存,就是只要程序申请内存并且内存条中有足够的内存,操作系统都会尽量满足,此时分配的内存就是堆内存。C++中的变量表示的内存就是栈内存。而堆内存则需要用其他方法来创建和处理。

指针和内存地址

上面说了变量简化表示内存,既然是内存就会有内存地址用来访问。可以使用&把变量表示的内存的地址取出来。以下代码就是在变量value前面加&,把它的内存地址取出来然后显示:

#include 

int main(void)
{
    int value = 100;
    std::cout << &value << std::endl; // 输出value表示的内存的内存地址
    return 0;
}

内存地址一般用十六进制的数值表示,也就是说内存地址就是数值。不过为了和普通的数值区分保存,就使用了特殊的变量来保存内存地址,这个变量就叫做指针

假设有一个double类型的变量,指针的类型就是变量的类型后面加上*,即指针的类型就是double *。以下代码就是取出变量value的地址并且用指针pointer保存,其中,int *pointer = &value;可以写成auto pointer = &value;:

#include 

int main(void)
{
    int value = 100;
    int *pointer = &value; // value类型是int, 所以pointer类型是int *
    std::cout << "变量value的地址:" << &value << std::endl; // 变量value的地址
    std::cout << "指针pointer保存的值:" << pointer << std::endl; // 变量pointer保存的值
    return 0;
}

指针是变量,换句话说,指针能够保存一个数值,它自身也是有内存的。所以同样也可以取指针的内存地址:

#include 

int main(void)
{
    int value = 100;
    int *pointer = &value; // value类型是int, 所以pointer类型是int *
    int **ptrtopointer = &pointer; // pointer类型是int *, 所以ptrtopointer类型是int **
    std::cout << "变量value的地址:\t\t\t" << &value << std::endl; // 变量value的地址
    std::cout << "指针pointer保存的值:\t\t" << pointer << std::endl; // 变量pointer保存的值
    std::cout << "指针pointer的地址:\t\t" << &pointer << std::endl; // 变量pointer的地址
    std::cout << "指针ptrtopointer保存的值:\t" << ptrtopointer << std::endl; // 变量ptrtopointer保存的值
    std::cout << "指针ptrtopointer的地址:\t" << &ptrtopointer << std::endl; // 变量ptrtopointer的地址
    return 0;
}

指针占用的内存大小

16位操作系统的指针大小是16位即2字节,32位操作系统的指针大小是32位即4字节,64位操作系统的指针大小是64位即8字节,128位操作系统的指针大小是128位即16字节,讲到这里,你是不是发现了什么。

实际上,以32位操作系统举例,操作系统为内存分配的内存地址是32位二进制的数值,而内存地址的最大值就是32位二进制的最大值,以GB为单位就是4GB - 1B。现在你就应该知道了吧,假设你操作系统是32位,你的内存条有8G,而操作系统只能用32位二进制的地址来表示内存,并且程序和操作系统都只能通过内存地址操作内存,那么32位操作系统就只能用内存条中的不足4G的内存,剩余的就是浪费了。

内存地址0 和 nullptr

有时候声明一个指针后暂时不需要保存变量地址,这个时候声明的指针请务必要初始化为nullptr,防止由于粗心大意对这个指针进行操作而导致程序中的数据错乱甚至程序崩溃。还是那句话,声明变量记得初始化。

int *pointer = nullptr;

关键字nullptr是只能赋给指针的一个值,它的意思是空指针。当你不再需要用到指针保存的地址时,就应该将指针赋值nullptr。

关键字nullptr实际就是内存地址值:0。内存地址0是一个特殊的内存地址,操作系统不会用地址0来表示内存位置。而当你对地址0进行读写操作时,程序就会崩溃。这样做的目的很简单,当不需要再用到指针保存的地址时,将指针赋值为空指针,在你不小心再对该指针进行操作的时候程序崩溃,你就会马上发现自己做错了。

在程序的内存中,保存着程序的各种执行代码指令和程序的数据,当你胡乱操作内存中的数据时,肯定会使程序出问题。为了数据的安全,大家都宁愿程序直接崩溃,也不愿意程序把数据都弄乱后再崩溃。

所以应该大胆地将不用的指针赋值为空,当你操作空指针后程序崩溃然后修改代码,总比你误操作导致数据错乱然后崩溃还不知所措的要好。

类类型的指针

基础示例

对于类类型的指针,这里以类std::u32string类型的指针作为例子:

#include 
#include 

int main(void)
{
    std::u32string text = U"小古银的C++教程";
    std::u32string *pointer = &text;

    // 获取字符串的字符数
    // auto length = (*pointer).size();
    // 上面这行代码可以简写成下面这行
    auto length = pointer->size();

    std::cout << length << std::endl;
    return 0;
}

输出结果:

9

基础讲解

当指针访问对象的成员变量或者成员函数的时候,例如上面代码中的成员函数size,按照之前的教程肯定是要这样写(*pointer).size()才能使用成员函数size,但是C++提供更简单的符号代替这种写法,就是上面代码中看到的->。那么(*pointer).size()就可以简写成pointer->size()。

补充知识(了解即可)

关键字nullptr从C++11开始引入,C++11前空指针用NULL或者直接用数字0代替,其中NULL也是字面量0。在现代C++中不推荐使用NULL,也不推荐给指针赋值数字0。

nullptr是指针的值,而NULL和0都是数字0,当调用重载函数是,这个区别就体现出来了:

void func(int value); // 函数1
void func(int *pointer); // 函数2

调用的时候:

func(nullptr); // 调用函数2
func(NULL); // 调用函数1
func(0); // 调用函数1