2 字符串
字符串是存储在内存的连续字节中的一系列字符;C++处理字符串的方式有两种,
- c-风格字符串(C-Style string)
- string 类
2.1 c-风格字符串(C-Style string)
2.1.1 char数组存储字符串(c-风格字符串)
将字符串存储在char数组中,每个字符都位于自己的数组元素中
以空字符(null character)结尾,空字符被写作\0,ASCII 为0,用于标记字符串的结尾。
不应将字符串的字符数组当做字符串来处理,末尾相差一个’\0’
缺点:
(1)造容易出现因’\0’造成的输出错误
(2)大量引号带来的输入压力;
2.1.2 字符串常量(string constant)
字符数组初始化为字符串,自动加上结尾的空字符:
char boss[8]="Bozo"
//隐式地包括结尾的空字符;
字符串常量(使用双引号)不能与字符常量(单引号)互换
char shirt_size = ‘S’; //fine 字符
char shirt_size = “S” // illegal
char shirt_size[ ]= “S” // fine 字符串
2.1.3 数组中 定义与初始化 字符串常量
- 定义与初始化
// 定义的同时初始化
char bird[]="Mr. Hellen"
cout << bird;
//先定义再初始化
char name1[20];
cin >> name1;
// input:lihua
cout << "hello"<< name1 << ", good morning!"
- 将键盘或文件输入读入到数组中(cin; getline; get)
cin :以空格、制表符和换行符确定字符串结束位置,这意味着(1)cin在获取字符数组输入时只能读取一个单词;读取后,cin将该字符串放到数组中,并自动在结尾添加空字符;(2) 无法防止输入溢出
getline(): 读取整行,在读取指定数目的字符 或 通过回车键输入的换行符来输入结尾;
cin.getline( arrayName , charNamber);
两个参数:第一个参数用来存储输入行的数组的名称。第二个参数要读取字符数;
e.g.cin.getline(name,20);
将姓名读取到一个包含20个元素的name数组中,这行包含的字符不超过19个。
- get():istream类中的一个函数
//用法1:类似于getline,但get不再读取或丢弃换行符,而是留在输入队列中;
const int Arsize = 20;
cin.get(name, ArSize);
cin.get(dessert,Arsize); //problem;读取到第一个字符是换行符,以为到行尾,无法跨过换行符继续输入;
//解决方案
//不带任何参数的cin.get(),可读取下一个字符;
cin.get(name, ArSize);
cin.get(); //read newline
cin.get(dessert,Arsize);
//合并写法:
cin.get(name, ArSize).get()
tips:使用建议使用get(),而不是getline,get()使输入更仔细,并且,老式实现没有getline;
get可以知道读取的原因是已经读取了整行,而不是由于数组已经填满;如果出现这种情况,查看下一个字符,如果是换行,那么说明读取了整行,否则,说明还有其他输入;
- 空行问题
cin >>year; // 输入 2024 回车;
cin.getline(address,80) // Wallstreet
//无法获得地址,因为getline读取到换行符后会以为是空行,并将空字符串赋给address
// 解决方案
cin >> year;
cin.get(); // or cin.get(ch);
(cin >> year).get(); //or (cin >> year).get(ch)
2.1.4 输出(访问)字符串
输出
使用cout 来显示string 对象。
cout << name1[ ];
访问存储在 string 对象中的字符
//获取字符数组元素值
cout << name1[0];
2.1.5 复制、拼接、附加字符串:
- 复制
strcpy(charr1,charr2);
- 拼接:注意目标存储过小,拼接溢出问题;
//拼接时不会在被连接的字符串之间添加空格
cout << "hel" << "lo, world!";
// cstring头文件
strcat(charr1,charr2);
strcat(charr1,"hello");
//strncat;strncopy;
2.1.6 sizeof 计算数组长度
// sizeof(name);
// strlen( )返回存储在数组中的字符串的长度,而不是数组本身的长度;
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
int main()
{
char name1[20]="lihua";
cout << name1 << endl;
cout << "the length of name1 is (sizeof) "<< sizeof(name1) << endl;
cout << "the length of name1 is (strlen) "<< strlen(name1) << endl;
return 0;
}
lihua
the length of name1 is (sizeof) 20
the length of name1 is (strlen) 5
2.2 string 类
包含在头文件string中;string类位于命名空间std中;
2.2.1 初始化
string str1; //声明创建一个长度为0的string对象
cin >> str1;
string str2 = "happy"
2.2.2 输出 cout
2.2.3 复制/赋值、拼接和附加
复制/赋值: str1 = str2; 一个string对象合一赋给另一个对象
拼接:str3=str1 +str2 ;
附加:str3 += “pro max”;
长度:int len1 = str1.size();
// str1不是作为参数,而是位于函数名前;str1是一个对象,size是一个类方法,方法是一个函数,只能通过其所属类的对象进行调用。
2.2.4 string的I/O
cin >> str; // read a word into the str string object;
getlline(cin,str) // 这里的getlinge不是类方法,而是将cin作为参数,指出到哪里查找输入;
R"( 和) " 作为定界符
cout << R"(Jim “King” Tutt uses “\n” instead endl.) "
//可在结果中显示’’ ‘’ () ;而不用 ‘’
输出:Jim “King” Tutt uses “\n” instead endl.
3 结构体
6 指针
指针是一个变量,其存储的是(所指向元素的)地址,而不是值本身。
&
——获取变量的地址;
*
——运算符被称为间接值或解除引用运算符,
*pointer 可得到该指针存储地址的值
指针manly ; manly表示一个地址,*many表示存储地址对应元素的值。
指针的定义:typeName *pointer;
初始化 int home=100; int *pointer = &home;
- 声明指针
TypeName * pointerName
double *pd;
char *pc;
- 初始化指针
double *pd;
double *pa
char *pc;
double pi =3.14;
pd = π
pc=new char;
pa =new double[30];
int *fellow;
*fellow =23333; //dangerous!野指针!
fellow = 0x1234567; //mismatch!等号两边一边是地址,一遍是数值;
fellow = (int *) 0x1234567;// match !等号两边均为地址,但是不提倡;
- 对指针解除引用:获得指针指向的值
cout << *pd;
// 输出pd存储的地址所指向的元素的值;输出结果为3.14;
*pc = 's';
// 修改pc存储的地址所指向的元素的值为s;
- *使用new&delete 管理内存
note:决不要对未被初始化为适当地址的指针解除引用,形成野指针;
所以一定要在对指针应用解除引用运算符*之前,将指针初始化为一个确定的、适当的值;
可以采用赋值法或者通过new来分配内存
使用new分配内存&使用delete释放内存
typeName * pointer_name = new typeName;
int* pt = new int;
//new运算符根据类型来确定需要多少字节的内存,接下来将地址赋给pt;
int happy; int *pn =&happy;
// 两种方法都是将一个int变量符地址赋给了指针。第二种通过名称happy来访问int,
第一种情况下只能通过该指针进行访问。我们可以说pt指向一个数据对象,指的是为数据项分配的内存块,好处是它使得程序在管理内存方面有了更大的控制权。
int* pt = new int;
*pt =1001;
cout << " int value = " << *pt << " location = " << pt << endl; //通过打印*pt打印值
sizeof(pt) //
sizeof(*pt) //返回值为4,int的4个字节的int值
地址只指出了指针所指向对象地址的开始,而没有指出其类型或长度信息,但是由于new声明了指针类型,因此程序知道*pt是4个字节(double的话是8个字节,指针pt所占用的字节是固定的)。
new从堆(heap)或自由存储区(free store)的内存区域分配内存。变量nights和pd的值都存储在栈(stack)的内存区域中。
释放内存
delete pt;
- 区分指针和指针所指向的值
double pi =3.14;
pd = π //地址;
cout << pd; //地址,输出的是pd存储的地址,即pi的地址;
cout <<*pd;// 值,输出pd存储的地址所指向的元素的值,即pi的值3.14;
- 数组名
在非字符串数组的情况下,C++将数组没那个视为数组的第一个元素的地址, 即array = &array[0];
int array[20];
`cout << sizeof(array);` //返回整个数组的长度;
`cout << sizeof(pointer)` // 返回指针所占存储空间大小
cout << array
// 数组名被解释为第一个元素的地址;等价于&array[0], 得到的是一个四字节内存块的地址;
//array+1 将地址值+4;
// array 可视为一个int指针(*int);
cout << &array
//得到的是整个数组的地址,一个20*4 =80字节内存块的地址;
//&array+1 将地址加80;
// &array可视作一个数组指针;
- 指针算术
指针和整数相加,等于在原来的地址值上加上指向的对象占用的总字节数。double 指针+1,指针数值增加8;
两个指向同一数组的指针相减,得到的是两个指针之间的元素个数
int tacos[10] = {0,1,2,3,4,5,6,7,8,9};
int *pt = tacos;
pt =pt +1; // pt存储/指向tacos[1],即1;
// tacos = tacos +1 是非法的;
int *pe =&tacos[9];
pe =pe -1; //指向tacos[8];
int diff = pe -pt; // diff 为7 tacos[8]和tacos[1]差了7个元素;
- 数组的动态联编和静态联编;
静态联编:通过数组声明创建数组,即数组的长度在编译时确定;
动态联编(动态数组):使用new[]运算符创建数组,即在运行时为数组分配空间,其长度也将在运行时设置。使用后,应使用delete[]释放所占用的内存;
int size;
cin >> size;
int *pz = new int [size];
…
delete [] pz;
- 数组表示法和指针表示法
c++ 将数组名解释为地址(首元素地址,数组地址);
tacos[1] == *(tacos + 1) == *(pt +1) == pt[1];