【C++】数组、字符串和字符串字面量

C++数组

  • 数组是元素的集合,按特定顺序排列的一堆东西
  • C++数组就是表示一堆变量组成的集合,一般是一行相同类型的变量
  • 数组就像在一个变量中有多个变量

例1

#include <iostream>

int main()
{
 int example[5];
 example[0] = 2;

 int a = example[0];


 std::cout << example[0] << std::endl;
 std::cout << example << std::endl;

 std::cin.get();
}
  • example数组
  • 已经为5个整型分配的空间
  • 如果像要访问-1或5,会出现内存访问违规(Memory Access Violation )(在debug模式会报错,但在release模式不会)
  • 注意:确保总是在数组的边界内写东西,不然的话,会导致很难调试的问题,因为修改了内存,而这些内存不是数组的一部分,会有可能是源代码中另一个变量的内存,这样的话,就会在不知不觉中修改了其他变量
  • 确保设置了安全检查,确保写的东西没有超出界限

for 循环

例2

#include <iostream>

int main()
{
 int example[5];
 for (int i = 0; i < 5; i++)
 {
  example[i] = 2;
 }


 std::cout << example[0] << std::endl;
 std::cout << example << std::endl;

 std::cin.get();
}
  • 索引从0到4,也可以写成<=4, 但一般不会这么做因为这涉及到性能问题,因为要多做一个等于的比较,所以,一般都写成<5

  • example是自己的内存地址,因为example是一个整型指针是连续的内存,每个整型占四个字节,总共5个整型,占20个字节
    在这里插入图片描述

  • 索引为2的话,则example地址偏移量为2*整型大小 = 2 * 4 = 8个字节

整型指针

例3

#include <iostream>

int main()
{
 int example[5];
 int* ptr = example;
 for (int i = 0; i < 5; i++)
 {
  example[i] = 2;
 }

 example[2] = 5;
 *(ptr + 2) = 6;

 std::cin.get();
}
  • 可以将整型数组赋值给整型指针
  • 指针可以用 逆向引用(dereference) 来修改数组
  • ptr + 2: 计算实际要加的字节数(偏移),但这个是取决于指针的类型,在这里,指针类型是整型,所以+2的效果相当于字节偏移量是4*2 = 8
  • 也可以将指针转换成只有一个字节的指针类型,比如char*类型:*(int *)((char* )ptr + 8) = 6
  1. 但是,如果将整型指针类型的ptr转换成char指针类型,+2就要变成+8
  2. 并且由于原类型是一个int*指针类型,所以加完偏移量之后需要著转回整型指针类型(int *)
  3. 最后,同样使用逆向引用修改指针指向的变量值

在堆(heap)上创建数组

  • 通过new关键字创建一个对象(实例)

例4

#include <iostream>

int main()
{
 int example[5];
 for (int i = 0; i < 5; i++)
 {
  example[i] = 2;
 }
 int* another = new int[5];
 for (int i = 0; i < 5; i++)
 {
  another[i] = 2;
 }
 delete[] another;

 std::cin.get();
}
  • 与之前的数组是一样的,只是生存期不同
  • example是在栈上创建的,在作用域结束的时候,就会自动销毁
  • another是在堆上创建的,直到程序销毁,都是出于活跃状态。需要用delete来删除
    由于是数组,需要带[]来删除它

为什么动态地用new来分配,而不是在栈上创建呢

  • 最大原因是生存期,用new分配的内存,直到手动删除都一直存在
  • 如果有一个函数返回一个数组,就必须使用new关键字来分配,除非传入一个数组的地址参数
  • 如果想返回一个数组,然而这个数组是在函数内创建的,就不会返回成功

间接寻址

  • 我们有指针,指针会指向另一个内存块,这个内存块保存着我们的数组,这会产生内存 碎片(memory fragmentation)缓丢失(cache miss)

例5

#include <iostream>

class Entity
{
public:
 int example[5];

 Entity()
 {
  for (int i = 0; i < 5; i++)
  {
   example[i] = 2;
  }
 }
};

int main()
{
 Entity e;
 
 std::cin.get();
}
  • Entity e的内存地址&e存储着:02 00 00 00 02 00 00 00 02 00 00 00 02 00 00 00 02 00 00 00
  • 然而,如果用new关键字在堆上创建:
#include <iostream>

class Entity
{
public:
 int* example = new int[5];

 Entity()
 {
  for (int i = 0; i < 5; i++)
  {
   example[i] = 2;
  }
 }
};

int main()
{ 
 Entity e;
 
 std::cin.get();
}
  • Entity e的内存地址&e存储着:58 76 78 00
  • 内存地址00787658存储着:02 00 00 00 02 00 00 00 02 00 00 00 02 00 00 00 02 00 00 00
  • 这个就是所谓的内存间接寻址(memory indirection)
  • 得到e的内存地址,它包含另一个内存地址,这才是存放数组的实际内存地址
  • 所以,当我们想要访问这个数组的时候,代码是跳来跳去的建议在栈上创建数组来避免这种情况,因为内存的跳跃肯定会影响性能

C++11的数组

  • std::array
  • 内置的数据结构
  • 有边界检查,记录数组大小
  • 原始的数组是无法计算出数组的大小的:
  1. 没有example->size()
  2. 永远不要在数组内存中,访问数组的大小,因为这是危险的
  3. int a[5]; int cout = sizeof(a) / sizeof(int);可以给出数组的大小
    sizeof(a) = 5*4 = 20
    sizeof(int) = 4
    cout = 20 / 4 = 5
  4. int* example = new int[5]; int cout = sizeof(example) / sizeof(int);
    sizeof(example) = 4
    sizeof(int) = 4
    cout = 4/ 4=1
  5. 但以上的方法不可靠,因为当把它放进某函数,或某些其他东西,它如果一旦变成指针,就完了
  • 所以,通常的做法是先定义一个size常量,并把这个size常量赋给数组的大小
  1. static const int exampleSize = 5; int* example[exampleSize]; 在栈中申请内存时,其大小必须是一个编译时就知道的常量(静态常量)
  2. const int exampleSize = 5; int* example = new int[exampleSize];或者在堆上申请内存,其大小不需要是一个静态常量
  • std::array:

例6

#include <iostream>
#include <array>


class Entity
{
public:
 static const int exampleSize = 5;
 int* example = new int[exampleSize];

 std::array<int, 5> another;


 Entity()
 {
  for (int i = 0; i < another.size(); i++)
  {
   example[i] = 2;
  }
 }
};

int main()
{ 
 Entity e;

 
 std::cin.get();
}
  • 会有一定的开销,因为会做边界检查
  • 也保存了一个整数size

C++字符串

  • 字符串是一个接一个字符的一组字符
  • 字符串是能够表示和处理文本的方法
  • 字符串实际上是字符数组

C风格字符串

例7

#include <iostream>

int main()
{
 const char* name = "Cherno";

 std::cin.get();
}
  • 也可以char* name = “Cherno”, 但通常不这么做,因为我们不希望改变这个字符串
  • 因为字符串是不可变的,不能扩展字符串并使它变大,因为这是一个固定分配的内存块
  • char* 不意味着是在堆上分配的,不能调用delete来删除

字符串

在这里插入图片描述

  • cherno后面接00, 表示结束符
  • 内存是十六进制,按照ascii码进行编码,43的ascii码是C

例8

#include <iostream>

int main()
{
 char* name = "Cherno";
 std::cout << name << std::endl;

 // name[2] = 'a';
 

 std::cin.get();
}
  • 假设name是一个数组指针,那么打印出来的是数组的地址;但是由于name是一个字符串指针,所以打印出来就是Cherno字符串
  • 双引号默认是char*
  • 字符数组:
  1. char name2[6] = { 'C', 'h', 'e', 'r', 'n', 'o' };单引号,在debug模型调试,会插入栈守卫之类的东西,这样就知道是不是在分配内存之外了
    在这里插入图片描述
#include <iostream>

int main()
{
 char* name = "Cherno";
 char name2[7] = { 'C', 'h', 'e', 'r', 'n', 'o', '\0' };
 int number[6] = { 0, 1, 2, 3, 4, 5 };
 std::cout << name2 << std::endl;
 std::cout << number << std::endl;

 std::cin.get();
}
  • 后面加一个\0

C++字符串String

  • BaseString类,是一个模板类
  • std::string, 是BaseString类的模板版本,模板参数是char,这个叫模板特化,template specialization
  • 将baseString模板类中的模板参数设为char,意思是char是每个字符背后的实际类型
  • std::string是什么?其实就是一组字符,以及背后一些可以操作的函数
  • string有一个构造函数,接受char或const char参数
  • string实际上是一个const char数组,而不是char数组

例9

#include <iostream>
#include <string>



int main()
{
 std::string name = "Cherno";
 std::cout << name << std::endl;

 std::cin.get();
}
  • 如果没有头文件#include 则输出流不允许字符串发送到cout流中,因为在string头文件内部,对操作符<<进行了字符串到输出流的重载

  • 两个字符串相加: std::string name = "Cherno" + "hello!"; 报错,因为右边是两个字符串相加,但是大家都知道,字符串是不可变的,字符串是const char数组,不是真正的string, 所以右边是不能相加的

  • 可以分开两行: std::string name = "Cherno"; name += "Hello!"; 这里的操作实际上是将一个指针,添加到了name,name是一个string,把它加到string上,而+=操作符已经被string重载了,所以可以这样写

  • 或者显式添加std::string强制转换: std::string name =std::string( "Cherno" )+ "Hello!";创建了一个string(“Cherno”),然后添加这个字符数组“Hello!”给这个string

  • 寻找字符串的文本,用find(): std::string name =std::string( "Cherno" )+ "Hello!"; bool constains = name.find("no") != std::string::npos; 在“Cherno”寻找"no",判断是否等于std::string::npos(即不存在的位置);返回判断结果;name.find()会返回“no”所在的首位置

字符串传递给函数

例10

void PrintString(std::string string)
{
 string += "h";
 std::cout << string << std::endl;
}
  • 不会这么写,因为std::string string 参数是一个拷贝
  • 这对外面传进来的string对象没有任何改变,这里的修改仅限于函数作用内

为什么要复制整个字符串

  • 意味着必须动态地在堆上分配一个全新的额char数组来存储已经得到的相同的文本
  • 字符串复制实际上是很慢的

确保常量引用字符串

void PrintString(std::string&  string)
{
 string += "h";
 std::cout << string << std::endl;
}

C++字符串字面量

  • 字符串字面量是在双引号之间的一串字符
  • 字符串最后有一个终止符

例11

#include <iostream>
#include <string>

#include <stdlib.h>


int main()
{
 const char name[8] = "Che\0rno";
 std::cout << strlen(name) << std::endl;

 std::cin.get();
}
  • char数组,name只有3个,就遇到了\0,结束

例12

#include <iostream>
#include <string>

#include <stdlib.h>


int main()
{
 const char* name = "Cherno";
 std::cout << strlen(name) << std::endl;

 std::cin.get();
}
  • char指针数组

  • 未定义是不被允许的,原因是,这里所做的是去了一个指向那个字符串字面量的内存位置的指针,而字符串字面量是存储在内存的只读部分
    在这里插入图片描述

  • 字符串存储在二进制文件的const部分,字符串常量被嵌入到二进制文件中。引用的时候,实际上引用到的是一个不能编辑的常量区域

  • char* name = "Cherno"; name[2] = 'a';这样是不行的,这是一个未定义的行为,改不了,不能将e改成a;试图对只读内存进行写操作,如果确实想要修改它,出于某些原因是可以的; clang编译器:用字符数组则没有那个问题

宽字符wchar_t

const char* name = "Cherno";
 const wchar_t* name2 = L"Cherno";

大写L是表示下面的字符串字面值由宽字符组成

const char* name = u8"Cherno" ;
 const wchar_t* name2 = L"Cherno" ;
 const char16_t* name3 = u"Cherno" ;
 const char32_t* name4 = U"Cherno" ;
  • wchar_t和char16_t之间的区别
using namespace std::string_literals;
 std::string name0 = std::string("Cherno") + "hello";

关于字符串字面量的内存

  • 字符串字面量永远保存在内存的只读区域内
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值