C++基础之指针(加精)

指针真正的用武之地在于,在运行阶段分配未命名的内存以存储值。在这种情况下,只能通过指针来访问内存。在C语言中,可以用库函数malloc()来分配内存;在C++中仍然可以这样做,但C++还有更好的方法——new运算符。

文章目录

  • 指针与数组
    • 深入探究
      • 探究一
      • 探究二
      • 探究三
      • 探究四
      • 探究五
      • 探究六
      • 探究七
  • 指针与字符串
  • 指针与结构
  • `new`和`delete`使用规则

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打印的是指针所指向的内存地址。

上述代码可以说明ptr1array1等价吗?不可以,其只能说明数组名array1存储了数组的第一个元素的地址,其是一个指针,且指向的是int类型数据对象。

array1ptr1一样,都可使用数组表示法与指针表示法:

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

探究二

ptr1array1究竟是否等价呢?答案是不等价:

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;

ptr1array1又是否等价呢?答案是不等价:

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 相差一个指针类型字节

ptr1array1一样,都指向数组第一个元素的地址,指向的都是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

ptr1ptr2本身都是指针,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::stringstd::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;

newdelete使用规则

  • 不要使用delete来释放不是new分配的内存。
  • 不要使用delete释放同一个内存块两次。
  • 如果使用new[ ]为数组分配内存,则应使用delete[ ]来释放。
  • 如果使用new 为一个实体分配内存,则应使用delete(没有方括号)来释放。
  • 对空指针应用delete是安全的。

你可能感兴趣的:(c++,c++)