指针真正的用武之地在于,在运行阶段分配未命名的内存以存储值。在这种情况下,只能通过指针来访问内存。在C语言中,可以用库函数
malloc()
来分配内存;在C++中仍然可以这样做,但C++还有更好的方法——new
运算符。
文章目录
int* ptr;
ptr = (int*)0xB8000000; //为指针赋明确地址
int* ptr1;
ptr1 = new int; //在C++中创建指针时,计算机将分配用来存储地址的内存,但不会分配用来存储指针所指向的数据的内存,为数据提供空间是一个独立的步骤。
*ptr1 = 22;
std::cout << *ptr1; //22
delete ptr1; //这将释放ps指向的内存,但不会删除指针ps本身
指针与数组
int* a=new int;
*a=22;
delete a;
int* b=new int[10]; //使用new来创建动态数组
b[0]=0; //数组表示法
*(b+1)=1; //指针表示法
delete[] b; //释放数组,方括号告诉程序,应释放整个数组,而不仅仅是指针指向的元素。
- 将指针变量加1后,其增加的值等于指向的类型占用的字节数。
- 使用数组声明来创建数组时,将采用静态联编,即数组的长度在编译时设置;使用
new[]
运算符创建数组时,将采用动态联编(动态数组),即将在运行时为数组分配空间,其长度也将在运行时设置。 - 可以把指针当作数组名使用,C和C++内部都使用指针来处理数组。数组和指针基本等价是C和C++的优点之一。
深入探究
探究一
int array1[10];
int* ptr1 = array1;
std::cout << array1 << " " << array1 + 1 << std::endl; //000000559DAFF618 000000559DAFF61C 相差一个int类型字节
std::cout << ptr1 << " " << ptr1 + 1 << std::endl; //000000559DAFF618 000000559DAFF61C 相差一个int类型字节
return 0;
始终牢记,对于指针而言,std::cout打印的是指针所指向的内存地址。
上述代码可以说明ptr1
与array1
等价吗?不可以,其只能说明数组名array1
存储了数组的第一个元素的地址,其是一个指针,且指向的是int
类型数据对象。
故array1
与ptr1
一样,都可使用数组表示法与指针表示法:
int array1[10];
int* ptr1 = array1;
//数组名
array1[0] = 100;
*(array1 + 1) = 200; //数组名使用指针表示法
//指针
*(ptr1 + 2) = 300;
ptr1[3] = 400; //指针使用数组表示法
std::cout << array1[0] << " " << array1[1] << " " << array1[2] << " " << array1[3] << std::endl; //100 200 300 400
探究二
那ptr1
与array1
究竟是否等价呢?答案是不等价:
int array1[10];
int* ptr1 = array1;
ptr1 = ptr1 + 1; //valid
array1 = array1 + 1; //invalid
编译器会报错,说表达式array1 = array1 + 1
必须是可修改的左值,编译器认为,array1
不可修改,即array1
是一个指针常量。指针常量的含义是:该类型指针,程序员不可以改变其指向, 但可以通过该指针改变其所指向的内存中所存储的值。
探究三
那么:
int array1[10];
int* const ptr1 = array1;
ptr1
与array1
又是否等价呢?答案是不等价:
int array1[10];
int* const ptr1 = array1;
std::cout << array1 << " " << array1 + 1 << std::endl; //000000F4B359F698 000000F4B359F69C 相差一个int类型字节
std::cout << ptr1 << " " << ptr1 + 1 << std::endl; //000000F4B359F698 000000F4B359F69C 相差一个int类型字节
std::cout << &array1 << " " << &array1 + 1 << std::endl; //000000F4B359F698 000000F4B359F6C0 相差整个数组字节
std::cout << &ptr1 << " " << &ptr1 + 1 << std::endl; //000000F4B359F6D8 000000F4B359F6E0 相差一个指针类型字节
ptr1
与array1
一样,都指向数组第一个元素的地址,指向的都是int
类型数据对象,且都是指针常量。
区别在于,array1
的地址,即&array1
,为000000F4B359F698
,它同样是数组第一个元素的地址,即array1
这个指针常量的地址与其所指向的内存的地址一样。诶,这是为什么呢?
&
是取址符,std::cout << &array1
打印的是array1
指针本身的地址,而不是指针所指向的内存地址。
探究四
很奇怪,为什么array1
这个指针常量的地址与其所指向的内存的地址一样,如果有C中多维数组的基础,应该会很好理解:
int array2[2][3] = { {1,2,3},{4,5,6} };
std::cout << &array2 << " " << &array2 + 1 << std::endl; //000000A86698FBC8 000000A86698FBE0 差6个int
std::cout << array2 <<" " <<array2+1<< std::endl; //000000A86698FBC8 000000A86698FBD4 差3个int
std::cout << *array2 << " " << *array2 + 1 << std::endl; //000000A86698FBC8 000000A86698FBCC 差1个int
std::cout << **array2 << std::endl; //1
array2
是一个二维数组名,它是一个二维指针,指向int[3]
类型,所以array2+1
增加了3个int
。
*array2
是一维指针,指向int类型,所以对*array2+1
增加了1个int
。
对于数组而言,解引用*
像是对array2
进行降维,对应的,取址符&
像是在对*array2
进行升维,升降维并不影响指针的指向,它仍是指向数组第一个元素的地址,只是改变了指向的类型。再次强调,这是对于数组而言。
基于此,应该就是能够理解,为何array2
和*array2
中存储的地址都是数组第一个元素的地址了。它们一个是数组名,一个是降维后的数组名。
那为什么&array2
也是数组第一个元素的地址?它貌似不再属于”对于数组而言“这样一个范畴了吧?
探究五
为什么&array2
也是数组第一个元素的地址?可以这样理解:
array2
为二维指针,指向的是数组的第一个元素地址;*array2
是对二维指针的降维,为一维指针,仍是指向数组的第一个元素地址。
从一维数组视角看,其实很难理解,为什么*array2
指向的是数组的第一个元素的地址,而*array2
的地址&*array2
也是数组第一个元素的地址。
但我们从二维数组视角看,&*array2
就是array2
,对于array2
而言,std::cout
输出的就是array2
所指向的地址,即数组第一个元素的地址。
如果说,&array2
属于”对于数组而言“这样一个范畴的话,那就十分合理了。那它是属于这个范畴吗?
探究六
array1究竟与什么等价?看如下代码:
int array1[10];
int(*ptr2)[10] = &array1;
std::cout << &array1 << " " << &array1 + 1 << std::endl; //0000005E4FB0F708 0000005E4FB0F730 相差整个数组字节
std::cout << ptr2 << " " << ptr2 + 1 << std::endl; //0000005E4FB0F708 0000005E4FB0F730 与 &array1等价
std::cout << array1 << " " << array1 + 1 << std::endl; //0000005E4FB0F708 0000005E4FB0F70C 相差一个int类型字节
std::cout << *ptr2 << " " << *ptr2 + 1 << std::endl; //0000005E4FB0F708 0000005E4FB0F70C 与 array1等价
(*ptr2)[1] = 200;
std::cout << array1[1] << std::endl; //200
可以这么理解,对于一维数组array1
,系统隐式创建了一个类型为int(*)[10]
的二维指针ptr2
,并将ptr2
对应的一维指针常量显式的开放为array1
数组名。(纯猜测,不负责)
基于"探究四"可知,array1
也指向数组的第一个元素地址,ptr2
指向array1
,同时ptr2
也指向数组的第一个元素地址,说明,array1
所指向的内存地址和其本身的内存地址是一样的。我不清楚C里面是如何实现这一点的,我们记住就好了。
这时我们可以回应”探究五“中的问题了,&array2
确实属于”对于数组而言“这样一个范畴,n
维数组其实隐式创建了一个n+1
维的指针,&array2
仍是在这一范畴内。
探究七
sizeof返回数据对象本身所占的字节数,探究sizeof
与指针、数组的作用关系:
int array1[10];
int* ptr1 = array1;
int(*ptr2)[10] = &array1;
std::cout << sizeof array1 << std::endl; //40
std::cout << sizeof &array1 << std::endl; //8
std::cout << sizeof ptr1 << std::endl; //8
std::cout << sizeof &ptr1 << std::endl; //8
std::cout << sizeof ptr2 << std::endl; //8
std::cout << sizeof *ptr2 << std::endl; //40
//多维数组
int array2[2][3] = { {1,2,3},{4,5,6} };
std::cout << sizeof array2 << std::endl; //24
std::cout << sizeof * array2 << std::endl; //12
std::cout << sizeof ** array2 << std::endl; //4
std::cout << sizeof &**array2 << std::endl; //8
std::cout << sizeof &* array2 << std::endl; //8
std::cout << sizeof & array2 << std::endl; //8
ptr1
与ptr2
本身都是指针,sizeof
返回指针变量本身所占字节数,8字节。
所有最后带&
的,都代表地址值,sizeof
返回地址值所占字节数,8字节(地址值所占字节数=指针变量所占字节数)。
对数组应用sizeof
得到的是整个数组所占字节数,多维数组类比;对指针解引用后应用sizeof
得到的是指针指向的数据对象所占字节数。
int array2[2][3] = { {1,2,3},{4,5,6} }; std::cout << sizeof * array2 << std::endl; //12 std::cout << sizeof array2[0] << std::endl; //12 std::cout << sizeof ** array2 << std::endl; //4 std::cout << sizeof array2[0][0] << std::endl; //4
*
与[]
某种程度上而言,两者是等效的。
指针与字符串
数组和指针的特殊关系可以扩展到C-风格字符串。不可扩展到std::string,std::string
本质是个类,不再是某个特定类型的数组。
char a[10] = "aaaa";
const char* b = "bbbb";
std::cout << a << " " <<b<<" "<<"cccc"<<std::endl; //aaaa bbbb cccc
数组名是第一个元素的地址,因此std::cout
语句中的a是char
元素的地址。std::cout
对象认为char
的地址是字符串的地址,因此它打印该地址处的字符,然后继续打印后面的字符,直到遇到空字符\0
为止。
在C++中,用引号括起的字符串像数组名一样,也是第一个元素的地址。上述代码不会将整个字符串发送给std::cout
,而只是发送该字符串的地址。这意味着对于数组中的字符串、用引号括起的字符串常量以及指针所描述的字符串,处理的方式是一样的,都将传递它们的地址。
在std::cout
和多数C++表达式中,char
数组名、char
指针以及用引号括起的字符串常量都被解释为字符串第一个字符的地址。
const char* a = "hahahaha";
std::cout << a << " " << (int*)a << std::endl; //hahahaha 00007FF63894BC30
一般来说,如果给std::cout
提供一个指针,它将打印地址。但如果指针的类型为char *
,则std::cout
将显示指向的字符串。如果要显示的是字符串的地址,则必须将这种指针强制转换为另一种指针类型。
指针与结构
struct student
{
std::string name;
int age;
};
student* stuPtr1=new student;
(*stuPtr1).name="Mr.Crocodile";
stuPtr1->age=25;
new
和delete
使用规则
- 不要使用
delete
来释放不是new
分配的内存。 - 不要使用
delete
释放同一个内存块两次。 - 如果使用
new[ ]
为数组分配内存,则应使用delete[ ]
来释放。 - 如果使用
new
为一个实体分配内存,则应使用delete
(没有方括号)来释放。 - 对空指针应用
delete
是安全的。