首页 > 编程语言 >C++基础之指针(加精)

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

时间:2024-08-31 22:50:35浏览次数:14  
标签:int array2 array1 C++ 地址 数组 加精 指针

指针真正的用武之地在于,在运行阶段分配未命名的内存以存储值。在这种情况下,只能通过指针来访问内存。在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打印的是指针所指向的内存地址。

上述代码可以说明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是安全的。

标签:int,array2,array1,C++,地址,数组,加精,指针
From: https://blog.csdn.net/weixin_45949932/article/details/141716360

相关文章

  • opencv/c++的一些简单的操作(入门)
    目录读取图片读取视频读取摄像头图像处理腐蚀膨胀调整图像大小裁剪和缩放 绘制绘制矩形绘制圆形绘制线条透视变换颜色检测轮廓查找人脸检测检测人脸检测嘴巴可适当调整参数读取图片读取路径widows使用vissto一定是\斜杠#include<opencv2/imgcodec......
  • 前K个高频单词 C++
    给定一个单词列表 words 和一个整数 k ,返回前 k 个出现次数最多的单词。返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率, 按字典顺序 排序。示例1:输入:words=["i","love","leetcode","i","love","coding"],k=2输出:["i","......
  • 双指针算法详解
      我的主页:2的n次方_       1.双指针算法双指针算法是一种在数组或字符串中常用且高效的算法技术,它通过维护两个指针(或索引)来遍历数据结构,从而解决某些问题。这种算法能够减少不必要的重复遍历,降低时间复杂度,并且往往能够使得代码更加简洁易懂。根据指针......
  • 【C++的创新性】C++11语法颠覆你的认知了吗?
    ​ 学习编程就得循环渐进,扎实基础,勿在浮沙筑高台   循环渐进Forward-CSDN博客Hello,这里是kiki,今天继续更新C++部分,我们继续来扩充我们的知识面,我希望能努力把抽象繁多的知识讲的生动又通俗易懂,今天要讲的是C++哈希~目录 循环渐进Forward-CSDN博客C++11简介......
  • 深入理解指针(4)(上)
    目录:1.数组名的理解2.&数组名的理解3.使用指针访问数组4.一维数组传参的本质1.数组名的理解上一章我们在模拟strlen函数时,使用了数组名进行了函数的传参,那么数组名到底意味着什么呢?#include<stdio.h>intmain(){intarr[5]={1,2,3,4,5};int*p=&arr......
  • 深入解析:如何在复杂 C++ 项目中高效集成 CMake 和 Conan
    目录标题第一章:C++项目中的Conan和CMake基础架构1.1项目架构概述1.2CMake与Conan的基本角色1.2.1CMake的角色1.2.2Conan的角色1.3在项目中合理结合使用CMake和Conan1.4实例分析1.5结语第二章:C++项目中的CMake和Conan实践2.1项目结构概览......
  • C++基础(1)——入门知识
    目录1.C++版本更新2.C++参考⽂档:3.C++书籍推荐4.C++的第⼀个程序5.命名空间5.1namespace的价值5.2namespace的定义5.3命名空间使⽤6.C++输⼊&输出7.缺省参数8.函数重载9.引⽤9.1引⽤的概念和定义9.2引⽤的特性9.3引⽤的使用9.4const引⽤ 9.5指针和引⽤的关......
  • C++ lambda 引用捕获临时对象引发 core 的案例
    今天复习前几年在项目过程中积累的各类技术案例,有一个小的coredump案例,当时小组里几位较资深的同事都没看出来,后面是我周末查了两三个小时解决掉的,今天再做一次系统的总结,给出一个复现的案例代码,案例代码比较简单,便于学习理解。1.简介原则:临时对象不应该被lambda引用捕获,因......
  • 算法题技巧之“枚举右维护左“--套路详细讲解带例题和易懂代码(Python,C++)
    本文参考:灵茶山艾府-力扣(LeetCode)        分享丨【题单】常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)-力扣(LeetCode)    本文主要讲解关于”枚举右维护左“这个刷算法题的技巧,包括简单的原理讲解和两个简单的例题(之后我也会总......
  • 【c++实用】CMakeLists 基本用法(一)
    指令解释cmake_minimum_required(VERSION3.24):指令指定了所需的最低CMake版本a.作用:确保在执行CMake配置时,使用的CMake版本不低于3.24。如果安装的CMake版本低于指定的版本,CMake将会输出错误信息,并拒绝继续执行后续命令b.兼容性:有助于确保项目构建脚......