0
点赞
收藏
分享

微信扫一扫

Cherno C++系列笔记8——P31~P32数组和字符串

M4Y 2022-03-25 阅读 34
c++

文章目录

1.P31 数组

参考:视频 笔记

1.1.数组和指针

#include<iostream>

int main()
{
		int example[5];
		int* ptr = example; 		// 数组名就是一个指针

		example[2]  =  2;
		*(ptr+2)  =  3;
		*(int*)((char*)ptr + 8)  =  4;

		std::cin.get();
}

数组存储的数据是连续的,在内存中每个整数是4个字节,所以我们得到的是一行20字节的内存。当我们通过example[i]来访问特定索引时,实际上是对内存取了一个偏移量。

注意数组名就是一个指针,它指向了这个数组内存的首地址。因为数组实际上只是一个指针,这里可以创建一个整型指针ptr,并赋值为example。

使用指针来访问数组的时候,若我们访问2号元素,则位置是从指针开始8个字节的偏移量。用指针简单地重写,指针算术上是ptr+2,然后解引用设置为想设置的数字。如果想使用字节偏移的地址,那么可以先把int指针转成char指针,然后偏移8位;最后赋值的之前为了不改变数组存储的数据类型,需要再转成int指针,最后解引用赋值。

1.2.手动计算数组长度的风险

#include<iostream>

int main()
{
         // 对:在栈上分配的数组,sizeof得到数组的字节数
		int example[5];
		int size = sizeof(example) / sizeof(int);
		
		// 错:在堆上分配的数组,赋值是一个指针,占内存就是4个字节
		int* new_example = new int[5];
		int new_size = sizeof(new_example) / sizeof(int);
		
		std::cin.get();
}

如上述程序,如果是在栈上创建的数组,那么sizeof得到的就是整个数组的字节大小,此时用sizeof统计就是正确的。

如果是在堆上new的数组,由于赋值给变量是一个指针,所以sizeof指针就是int大小,即4个字节。这样统计就是错误的。

因此必须手动维护数组的长度,即声明一个const变量。但是如果直接这样声明的话,在栈上分配数组的时候会报错,因为在栈上分配数组的时候大小必须是一个编译时就知道的常量。解决办法是把const变量声明为static,static被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间

class Entity
{
public:
		// 错误方法
		const int size = 5;
		int example[size];			// 报错,因为在栈上为数组申请内存的时候,数组大小必须是一个编译时就知道的常量

		// 正确方法:
		static const int size = 5;
		int example[size];	
};

1.3.在堆上创建数组

1.3.1.堆栈数组的不同

在栈上创建,当跳出作用域范围时,它会被销毁。

为什么要动态地使用new来分配,而不是在栈上创建呢。最大的原因还是生存期的不同,用new分配的内存将一直存在直到删除它。如果是在堆上创建的话,直到程序把它销毁之前都是处于活动状态的,所以我们需要用delete关键字来删除。因为使用了数组的操作符[]来分配内存,我们还需要使用[]来删除它。

#include<iostream>

int main() 
{
		int example[5]; //在栈上创建
		int* another = new int[5]; //在堆上创建
		delete[] another;
		std::cin.get();
}

1.3.2.堆上创建数组的间接寻址

#include<iostream>

class Entity
{
public:
         // 栈
		int example[5];
		// 堆
		int* example = new int[5];
		
		Entity()
		{
				for (int i = 0;i < 5;i++)
					example[i] = 2;
		}
};

int main()
{
		Entity e;
		
		std::cin.get();
}
  • 栈:类的地址就是成员变量中数组的首地址
  • 堆:类的地址中存放的是一个指针,这个指针所指向的内存才是成员变量中数组存放的地方。所以应该在栈上创建数组来避免这种情况,因为这样在内存中跳跃肯定会影响性能。

1.4.C++11中的数组

在C++11中我们有标准数组std::array,这是一个内置数据结构在C++11库中。它有很多优点,例如边界检查,记录数组大小(原始数组无法计算大小,无法使用example.size())。

std::arry<int,5>another;

此时的another.size()大小为5。

2.P32 字符串

参考:视频 笔记

2.1.字符串本质

字符串实际上是字符数组,我们通常将字符串称为const char*。加上const是为了不改变这些值,因为无法扩展字符串使其变大。char*并不意味着它是在堆上分配的,不能通过调用delete来删除。

const char* name = "Cherno";

我们可以在内存视图的右边看到Cherno这个词,说明左边数字代表的是ASCII值。第7个字节被设为0,这被称为空终止字符,空终止字符是为了判断字符串的size。字符串从指针的内存地址开始,直到碰到0为止。

在这里插入图片描述

2.2.std::string

C++的标准库中有一个模板类叫BasicString,而string的类实际上就是BasicString类的模板参数是char。在c++中使用字符串应该使用std::string,它只是一个char数组和一些操作这些数组的函数

string有一个构造函数,它接收char或const char参数,把鼠标悬停会发现实际上是一个const char数组而不是char数组。为什么通常把字符串赋值给const char* 而不是char*,因为本质上用双引号定义字符串时,在C++中是const char数组而不是char数组,这是通过char* 的隐式转换
在这里插入图片描述

2.3.追加字符串

若想在Cherno后面加上hello,这里可能会出错。因为我们是想把两个const char类型的数组相加(显然不合法),双引号里的东西是const char数组而不是真正的字符串,不能把两个指针(数组就是指针)相加,不能将两个数组直接相加。

若想做这样的事,我们可以把它分成多行,这样做是将一个指针加到了name,name是一个字符串,把它加到字符串上,+=这个操作符在string类中被重载了。
在这里插入图片描述

2.4.字符串作为参数传递要使用引用

如果我们写了一个叫PrintString的函数想要传递一个字符串,不会简单的写std::string string然后打印string。

void PrintString(std::string string) 
{
	std::cout << string << std::endl;
}

这实际上是一个副本,如果这样把类(对象)传递给一个函数,实际上是在复制这个类(对象),这样会在堆上创建一个char数组然后复制string中的每个字符。在函数中对这个类(对象)进行的操作不会传递到原始的类(对象)中。

void PrintString(const std::string& string) 
{
	std::cout << string << std::endl;
}

可以通过常量引用来传递,在前面加上const和引用&。&告诉我们是一个引用,意味着它不会被复制,而const承诺我们不会修改它(string)。

举报

相关推荐

0 条评论