数组和指针

数组知识的查缺补漏

对未初始化的数组打印时会输出 -858993460 因为Visual Studio编译器在Debug版本会用这个数据来填充未初始化的内存块,如果在发布的Release版本可能会是脏数据了。虽然能过编译并输出但是毫无意义

在这里插入图片描述

数组的本质就是就是申请了一段连续的内存空间:如下图示,该arr数组是int类型,所以申请了5个连续的int类型大小的空间。

在这里插入图片描述

在这里插入图片描述

int main() {//for循环的新写法
	int arr[5]{ 65,66,67,68,69 };
	for (char data : arr) {//相当于char data = arr[i];
        //C++11后支持的写法,我觉得这样的写法虽然不能知道当前遍历到的index,但是只需要数据时简洁快速
		cout << data << endl;//输出A B C D E
	}
}

二维数组

有一个arr[2][3]二维数组,同理在内存也是连续的

但是内存连续情况是arr[0][0]—arr[0][1]—arr[0][2]—arr[1][0]—arr[1][1]—arr[1][2]【总是低维先连续后连续高维】

//1
for(int i = 0; i < 2; i++){//遍历行
    for(int j = 0; j < 3; j++){//遍历列
        cout<<arr[i][j]<<endl;
    }
}
//2
for(int j = 0; j < 3; j++){//遍历列
    for(int i = 0; i < 2; i++){//遍历行
        cout<<arr[i][j]<<endl;
    }
}

结论:方法1的遍历方式比方法2遍历方式效率快。

原因:cache高速缓存结合时间局部性和空间局部性

cache:存在cache机制(后续会要深入讲解,这里简单解释)。【我们要学习,必须带书包,书要放在桌子上学习,桌子很近的旁边有个书架。这里的书包理解为内存,桌子上理解为CPU,书架理解为cache。书架的书一眼就看到和伸手就拿到耗时1s,而书包的书必须打开拉链拿出书后拉上拉链耗时10s。假设此时书架的容量只有3本书,为了充分利用,当我需要1号书时,我去拿1号书时会顺手把2、3号书一把手放到书架,因为2,3号书是连续放在书包的且可能后续会用到而提前做准备。如果突然要4号书又要去打开书包,又把书架的2、3号数换成5,6号书,如果后续又要拿2号书时,这样对书架的作用机制并没有体现出来】

方法1遍历,每次遍历连续的,则只会换书架的书2次,而且每次拿书架的书很快很少去打开书包。

方法2遍历,每次遍历不连续的内存空间,导致我们访问书包次数过多,书架机制没有利用好。

时间局部性:时间局部性指的是在一段时间内,程序很可能再次访问之前已经访问过的相同数据或代码。

空间局部性:空间局部性指的是程序在一段时间内很可能访问彼此相邻的内存位置。

std::array和std::vector

array

因为原生数组存在的问题如定了就固定大小,没有边界检查的安全问题,和动态操作性低,且无法提供类似数组长度信息等等的问题

因此C++标准库提供了一个容器来初步处理解决原生数组可能的问题。

std::array<变量类型,数量> 容器名;

【array几乎没用过,因为后续还有更高级的容器vector】

vector和array联系和区别

相同顺序存储迭代器支持元素访问
都内存顺序的方式存储元素,可以通过索引访问元素。都可以使用迭代器进行元素的遍历。都可以使用 operator[] 和 at() 方法访问元素
区别大小动态性内存管理性能参数传递底层实现
array是一个静态数组,其大小在编译时确定,不能在运行时改变。它在栈上分配内存,因此没有动态内存分配的开销。使用栈上的内存,大小是固定的,由数组类型和大小在编译时确定。在大小固定的情况下可能更快,因为它避免了动态内存管理的开销。可以作为值传递,因为其大小在编译时已知。元素通常被直接存储在栈上,而不是使用动态内存分配
vector是一个动态数组,可以在运行时改变其大小。它通过动态内存分配来管理元素。堆上使用动态内存分配,可以自动调整大小以适应元素的数量。可以更灵活地处理大小的变化,但可能涉及到动态内存分配和释放的开销。使用动态内存,传递它作为函数参数时可能涉及到指针或引用,而不是直接传递整个数组。通常使用动态内存分配(堆上的内存)来存储元素

指针

指针是个变量,存储着该进程的虚拟内存地址,也就是存储着地址。指针可以通过存储的地址值解引用*(间接运算符)得到地址对于的内存值,其他正常变量数据可以用引用&得到变量的地址

指针不论什么类型都是一致的大小,只取决于操作系统位数,即32操作系统支持4GB内存即4字节指针大小,64操作系统则8字节大小指针。

int* a,b;//这样的声明方式a是int指针,但是b是int数据
int  a,* b;//必须前面跟着* 才能是使得b是int指针
int data = 5;
int* ptr = &data; 
//*ptr++和(*ptr)++区别
cout << *ptr++ << endl;
//输出ptr指针指向的地址对应的数据后 指针++操作即移动指针指向的的类型所占的内存大小,如这里移动了int 4B大小
cout << (*ptr)++ << endl;//对指向的内存区域的数据大小加一即可,不移动指针
指针数组
int* ptrArr[5];//声明5个int*指针的数组

①&arr是一个指向"整个"二维数组的指针,对其输出&arr+1会偏移整个内存的大小即24B

②arr为指向第一行的起始地址的指针对其输出arr+1会打印出偏移一行大小的地址8B

③arr[0]会表示为指向每行的具体哪个数据的指针,对arr[0]+1打印地址为偏移4B

指针的类型转换:

  • 类型大小相同时:int和unsigned是占用32位4B字节
    • 当创建int*指针指向unsigned数据的地址时,要对该地址类型转换为(int*)
    • 如上的操作是用int*指针指向的地址是无符号数类型时 ,在不越数据边界时还好,当越界时,如此时用int指针对其地址按照int规则修改数据为-1,那么对于该操作在int指针下的int规则,该地址的二进制则变为32个1(32个1在int规则下首位符号位为1按照补码存储)。所以当用int指针类型来解引用按照int规则翻译该内存二进制代码会翻译成-1,而当用无符号类型访问时会用无符号数据类型规则翻译32个1,即为数据232
  • 类型大小不同时:long long数据类型占用64位8字节,char类型占用1字节4位
    • 由4字节转到8字节。long long数据类型占用64位8字节
      • 创建long long*指针指向unsigned数据的地址时对该地址类型转换为(long long*)。创建char*时同理。
      • 上面也说了unsigned数据的地址被修改为32个1了,假设我们对long long指针解引用,按照long long的规则读取内存,原本的int指针int规则只读了4B,long long会往高地址内存多读4字节32位。现代大部分都是小端存储方式,所以读取内存时会多读到高位32位0,也就是二进制代码为 32个*+32个1,其中32个*是未知内存区域,所以long long规则读取的数据难以判定
    • char类型占用1字节4位
      • 创建同理的类型转换
      • 当我们用char*指针来解引用修改按照char规则修改时,只会修改1个字节,如32位1,用char*指针修改为’A’时(ASCII码的十进制为65,即十六进制41),那么按照该规则修改后,只修改了低位地址,也就是按照小端方式的低位数据,即二进制数据变成了24个1加0100 0001也就是FF FF FF 41

对指针的运算++,因为指针表示十六进制的地址,也就是每加一次,会加上1*指针大小。

指针也是变量,所以也有存放在内存的地址,可以创建多级指针如二级指针指向一级指针的地址嵌套到多级指针

int a = 2;
const int* ptr1 = &a;//指向可以改,指向的值不可以;这是常量指针
int* const ptr2 = &a;//ptr2地址不可改,所以指向不能改;这是一个指向常量的指针,指针常量	
const int* const ptr3 = &a;//不可以改指向和指向的值
//????
const int a = 10;
const int *const ptr = &a;
int *ptra = (int *) &a;
cout << *ptra << endl;
*ptra = 99;
cout << *ptra << endl;
cout << a << endl;
/*
尽管通过非常量指针 ptra 修改了 a 的值,但这违反了 a 的常量属性。编译器可能会对这种情况进行优化,使得对 a 的修改在实际上并没有真正反映到内存中,而是在寄存器之间进行了处理。因为编译器可能会对这种不合法的修改进行优化或者忽略。这再次强调了遵循编程语言规范的重要性,以确保代码的可靠性和可移植性。
*/

指针可以当数组用也就是用数组的方式使用指针,但是数组不一定能当指针用:指针可以随便指向其他数组,数组不可以当作指针随便指,

因为数组的那个地址本质是个地址而不是变量。

int a[5];
int *ptr = a + 1;//指针指向a[1]
a[0] = 3;
a[1] = 5;
a[2] = 55;
cout<<ptr[1]<<endl;//所以把a[1]当成ptr[0],而a[2]为ptr[1]
int arr[2][4] = {
      {1, 2, 3, 4},
      {5, 6, 7, 8},
};
int *test[5];//五个int的指针。指针数组
int (*ptr)[4] = arr;//数组指针
cout << ptr[1][1] << endl;
cout<<ptr<<endl;
ptr++;//对数组指针加加时是加上4*int大小
cout<<ptr<<endl;
//sizeof(ptr)=4
//cout<<sizeof(test)=40

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值