计算机的内存模型
CPU是计算机的核心部件,要想让一个CPU工作,就必须向它提供指令和数据,指令和数据在存储器中存放,也就是我们平时说的内存。
内存分为:物理内存和虚拟内存,物理内存对应着计算机中的内存条,虚拟内存是操作系统内存管理系统假想出来的,由于这些不是我们本文的重点,我们就不做区分。
在不考虑cpu缓存的情况下,计算机运行程序本质上是对内存中的数据的操作,存储器被划分为多个存储单元,存储单元从零开始顺序编号,CPU要从内存中读取数据,首先要指定存储单元的地址。
CPU从内存中读取数据的过程如图所示:
计算机为了方便管理内存,将内存的每个单元用一个数字编号
指针与指针常量
指针的本意就是内存地址,我们可以通俗的理解成内存编号,既然计算机通过编号来操作内存单元,这就造成了指针的高效率
指针变量:
- 通俗理解为存储指针的变量,也就是存储内存地址(内存编号)的变量
-
指针变量和int,float,char等类型一样同属变量类型,指针变量类型占四个字节(32位机器下),存储的是32位的内存地址
星号: - 在C\C++中(*)被定义为取内容符号
- 虽然所有指针变量占的内存大小和存储的内存地址大小都是一样的,但是由于存储的只是数据的内存首地址,所以指针变量存储的内存地址所指向的数据类型决定着如何解析这个首地址
- 比如int型指针变量,我们需要从该指针变量存储的首地址开始向后一直搜索4个字节的内存空间
- 所以当我们使用*p,必须知道p是一个什么类型的指针
指针变量和指针常量
指针变量首先是一个变量,由于指针变量存储了某个变量的内存首地址,我们通常认为"指针变量指向了该变量",同时指针变量时一个变量,它的值是可以变动的。
相反,指针常量可通俗地理解为存储固定的内存单元地址编号的量,它一旦存储了某个内存地址以后,不可再改存储其他的内存地址了
举个例子:
1
2
3
4
5
6
|
void f( const int *x, int *y) { *x=2; //错误,由于x前面有个const修饰,所以不可以修改x所指向的内存单元的内容 //正确写法 *y=3; } |
指针变量和数组
先看一个例子:
1
2
3
4
|
int a[5]={1,2,3,4,5}; int *ptr=( int *)(&a+1); cout<<*(a+1)<<endl; cout<<*(ptr-1)<<endl; |
输出结果为2和5,首先我们看一下&a+1的含义:
- 我们知道C\C++中规定数组名表示这个数组的首地址,而这里出现了&a这样的符号,本来a就是指针常量,再次取地址难道不是非法操作?
- 这时我们可以将这个&a看成是指向数组的指针,也称为行指针,&a的类型是int (*p)[5],一个步长即5个元素的长度,&a + 1代表往后移动一个步长
分析:
- a表示的是第一个元素的首地址,那么a+1指向的就是下一个元素的内存首地址,所以*(a+1)=2
- 而&a则表示整个数据的首地址,那么&a+1移动的内存数目就是整个数组所占字节数,假如原先数组中第一个元素的首地址是0,那么&a+1表示的就是20,而这个地址已经不属于数组了,接着通过(int*)(&a+1)将数组指针转换成整型指针,这样原先&a+1表示的数据范围20-39就缩小为20-23,正好是一个int型的大小,而ptr-1就是16了,表示的数据内存范围是16-19,这样*(ptr-1)正好是最后一个元素5了
上面的例子,只是通过简单的数据类型来说明内存分布,但是实际上一些复杂的数据类型,尤其是一些自定义的类或者结构体类型,内存分布还要充分考虑到字节对齐。
函数指针
函数指针是指向函数的指针变量,C\C++程序在编译时,每个函数都有一个入口地址,该入口地址就是函数指针所指向的地址,有了指向函数的指针变量后,可用该指针变量调用函数,同时也可以做函数的参数
我们先看函数指针调用函数,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
int f( int x, int y) { return x + y; } //申明一个函数指针 typedef int (*pf)( int , int ); int main() { int a = 1; int b = 2; //将函数f地址赋给函数指针pf pf = f; //利用函数指针调用函数 int c = (*pf)(a, b); cout << c << endl; } |
需要注意的是,定义的函数指针类型时的函数签名(包括函数返回值和函数参数列表的类型,个数,顺序)要将赋值给该类型变量的函数签名保持一致,不然可能会发生很多无法预料的情况,还有C\C++规定函数名就表示函数入口地址,所以,函数名赋值时函数名前面加不加取地址符&都一样,但是在C++中取类的方法函数的地址时,这个&符号不能省略。
函数指针还有另外一个用处,就是作为一个函数的参数,在Windows编程中作为回调函数很常见:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
typedef int (*PF)( int , int ); int f1( int x, int y) { return x + y; } int f2(PF pf, int t) { return (*pf)(3, t); } int main() { //将函数f1作为参数传递给函数f2 int c = f2(f1, 4); cout << c << endl; return 0; } |
C++中的引用
所谓引用,使用另外一个变量名来代表某一块内存,这就相当于同一个人有不同的名字,但是不管哪个名字,指的都是同一个人。
1
2
3
4
5
|
int a=1; //通过&符号,将b定义为a的引用 int &b=a; //b和a完全一样,等价于int c=a int c=b; |
注意,C++规定,定义一个引用的时候,必须马上初始化
传值还是传引用
如果变量类型是基元数据类型,比如int,float,bool,char等小数据类型被称为基元数据类型,那么赋值时传的是值,这时候b的值是a的拷贝,那么更改b不会影响到a,但是,如果变量数据类型是复杂数据类型,比如数组,类对象,那么赋值时传的就是引用,这个时候,a和b指向的都是同一个内存区域,那么无论更改a或者b都会相互影响。
最后,在利用C++中拷贝构造函数复制对象时需要注意,基元数据类型可以直接复制,但是对于引用类型数据,我们需要实现引用类型的真正复制
C++中的new关键词
在c++中通过new关键词定义一个对象,不能直接得到对象的实例,我们需要用一个指针去接收这个new出来的对象,我们引用这个对象必须使用指针引用运算符->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
#include <iostream> using namespace std; class Person { public : Person() { } Person( int a, int b) { this ->m_a = a; this ->m_b = b; } int m_a; int m_b; }; int main() { //在C++中可以用以下形式来实例化一个对象,per1和per2为实例化的person类对象 Person per1; int i = 1; int j = 2; Person per2(i, j); } |
在C++中,this关键词是一个指针,而不像在java中是一个类实例,在C++中*this才等价于java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class Person { public : Person( int number) { //C++中需要使用指针引用符号 this ->m_number = number; } //返回对象本身,,需要使用引用,因为返回值时会创建一个新的对象,使用引用的方式不会创建新的对象 Person& getSelf() { return * this ; } int m_number; }; |
总结
到此这篇关于C/C++中指针的文章就介绍到这了,更多相关C/C++指针理解内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!
原文链接:https://blog.csdn.net/m0_51174487/article/details/118936420