Cherno c++笔记[31-33]

Cherno c++笔记

【31】Array 数组

  • 数组是元素的组合,一堆变量组成的集合,一般是相同类型的变量
  • 数组名不是一个指针,但可以被隐式地转换为指向数组第一个元素的指针,用于访问整个数组或其部分元素
    内存访问违规(Memory access violation):在debug模式下,你会得到一个程序崩溃的错误消息,来帮助你调试那些问题;然而在release模式下,你可能不会得到报错信息。这意味着你已经写入了不属于你的内存。所以要确保,总是在数组的边界内写入。
//定义一个含5个整数的数组  
    int a[5];
//访问
    a[5],a[-1]; //内存访问违规(Memory access violation)

数组就是在内存中连续的数据块

int main() 
{
	int example[5]; // 一个数组就是一个指针,这里是一个指向包含5 个整数的内存块的整形指针。即20个byte
	int* ptr = example;
	for (int i = 0; i < 5; i++)
		example[i] = 2;

	//下面三行的作用是相同的,第一行利用的数组的原理,数组只是一个连续的数据块
	example[2] = 5;
	*(ptr + 2) = 6; //ptr是int指针,4个byte,所以再向后偏移2x4=8个byte
	*(int*)((char*)ptr + 8) = 7;//将ptr转化为char型指针,char是1个byte,所以向后+8
	std::cin.get();
}

在堆(heap)上创建数组,如果有一个函数返回一个数组,必须使用new来分配它,除非传入一个数组的地址参数。

内存间接寻址
在栈上创建整形数组

class Entity
{
public:
    int example[5]; //栈数组
    Entity()  //创建一个构造函数,用来初始化所有值为2
    {
        for (int i = 0; i< 5;i++) 
             example[i] = 2;   
    }
};

int main()
{
    Entity e; //实例化一个对象。如果我们查看Entity e的内存地址,可以看到Entity的内存上实际就是一行,包含了数组中所有的2,所有的数据都在这儿

    std::cin.get();
}

在堆上用new关键字创建数组

class Entity
{
public:
    int* example = new int[5];  //堆数组
    Entity()  
    {
        for (int i = 0; i< 5;i++) 
             example[i] = 2;   
    }
};
int main()
{
    Entity e; //这时我们查看Entity e的内存地址,可以看到这里的内存根本没有2。我看到另一个内存地址,其实这就是个指针。我可以复制该地址然后粘贴查找(因为endian(字节序)的原因我必须要反转它们),然后就可以看到我真正的数据。这就是memory indirection(内存间接寻址)
    std::cin.get();
}

我们在e地址上实际上得到的另一个地址,这个地址指向我们实际的数组。这意味着如果我们要访问这个数组,我们基本上要在代码里跳来跳去,先找到Entity,然后再找到数组。

如果可以你应该在栈上创建数组来避免这种情况。因为像这样在内存里跳跃肯定会影响性能

c++11中的数组

标准数组std::array,这是一个内置数据结构,定义在C++11的标准库中。很多人喜欢用它来代替这里的原生数组,因为他有很多优点,它有边界检查,有记录数组的大小
实际上我们没有办法计算原生数组的大小,但可以通过一些办法知道大小(例如因为当你删除这个数组时,编译器要知道实际上需要释放多少内存)。

计算原生数组大小
创建一个栈数组,你不知道他的实际大小,因为它是在栈上分配的,也就是说这是栈上的地址加上偏移量

int a[5]; //栈数组

所以如果你写:

sizeof(a); //20bytes

如果你想知道有多少个元素,可以用sizeof(a)除以数据类型int的大小,得到5

int count = sizeof(a) / sizeof(int); //5

但是如果你用堆数组example做同样的事:

int* example = new int[5];  //堆数组
int count = sizeof(example) / sizeof(int); //1

你再这里得到的实际上是一个整形指针的大小(int* example),就是4字节,4/4就是1。

所以只能在栈分配的数组上用这个技巧,但是你真的不能相信这个方法!当你把它放在函数或者它变成了指针,那你完蛋了(因为“栈上的地址加上偏移量”)。所以你要做的就是自己维护数组的大小。

【32】字符串

字符其实就是char类型,字符串实际上是字符数组。经常将字符串称为const char*。前面加上const,是因为字符串是不可变的,不能扩展字符串,这是一个固定分配的内存块。

字符串在内存中是什么样的?怎么工作的?

在这里插入图片描述内存视图中,左边是内存的每个byte,用16进制表示,右边是ascii码对应的值。参展ASCII码对照表可以对的上。内存结尾处有一个空终止字符。字符串读取内存,从指针的地址起点开始,到终止符结束。
c++中字符默认用’ ',char*指针默认用" ".

const char* name = "cherno"; //c风格字符串
char* name = "cherno" //报错,因为C++中默认的双引号就是一个字符数组const char*
char name[3] = {'l','i','u'};//报错,缺少空终止符
char name[3] = {'l','i','u',0};//正确
char name[3] = {'l','i','u','\0'};//正确,因为ascii码'\0'就是null

如何在c++中使用字符串

应该使用std::string
std::string 是 C++ 标准库中的一个字符串类型,它是一个类模板,用于存储和操作字符串。既可以代表一个字符串对象,也可以代表一组字符序列。
与 C 语言的字符串不同,std::string 可以自动调整存储空间大小,具有更好的安全性和易用性。它提供了许多成员函数,例如拼接、查找、替换、插入、删除等等,可以方便地对字符串进行各种操作。
以下是 std::string 的一些常用方法:

  • length() 或 size(): 返回字符串的长度。
  • append(str) 或 += str: 将字符串 str 追加到当前字符串的末尾。
  • find(str, pos=0) : 从 pos 开始查找字符串 str,并返回第一个匹配的位置,如果没有找到则返回 std::string::npos。
  • replace(pos, len, str) : 从指定位置 pos 开始,将长度为 len 的子字符串替换为字符串 str。
  • substr(pos, len) : 返回从指定位置 pos 开始,长度为 len 的子字符串。
  • insert(pos, str) : 在指定位置 pos 处插入字符串 str。

【33】字符串字面量(String Literals)

  • 字符串字面量就是双引号中的内容。
  • 字符串字面量是存储在内存的只读部分的,不可对只读内存进行写操作。
  • C++11以后,默认为const char*,否则会报错。
别的一些字符串

基本上,char是一个字节的字符,char16_t是两个字节的16个比特的字符(utf16),char32_t是32比特4字节的字符(utf32),const char就是utf8. 那么wchar_t也是两个字节,和char16_t的区别是什么呢?事实上宽字符的大小,实际上是由编译器决定的,可能是一个字节也可能是两个字节也可能是4个字节,实际应用中通常不是2个就是4个(Windows是2个字节,Linux是4个字节),所以这是一个变动的值。如果要两个字节就用char16_t,它总是16个比特的。

   const char* name = "lk";
   const wchar_t* name2 = L"lk";
   const char16_t* name3 = u"lk";
   const char32_t* name4 = U"lk";
   const char* name5 = u8"lk";
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值