6.3c++primeplus总结(p62-p91)

content

  • 1.字符串(string)
    • (1)c语言风格的字符串
    • (2)c++风格的字符串
  • 2.共用体(union)和枚举(enum)
    • (1)共用体(union)
    • (2) 枚举(enum)
  • 3.指针

1.字符串(string)

字符串是定义在内存的连续字节中的一系列字符。字符串有两种风格:

  1. c语言风格的字符串
  2. c++风格的字符串

(1)c语言风格的字符串

1.字符串越界问题
学过c的都知道,当我们需要存储一个字符串的时候,一般会这样定义:

#include<cstdio>
#include<cstring>
int main()
{
	char str[14] = { 'i',' ','a','m', ' ','a',' ','s','t','u','d','e','n','t' };
	printf("该数组有%d元素\n", strlen(str));
	printf("输出该字符串:\n%s", str);
	return 0;
}

首先,我们定义的是14个字符空间,而“i am a student"一共也是14个字符,但是运行程序会发现,该字符串的长度是54,并且输出字符串:i am a student烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫?峫?

会出现这种情况的原因是什么?
答案是,在初始化str字符串的时候结尾没有赋予字符串结束字符’\0’。 在计算机内部,处理字符串是这样的:从数组开头逐个处理字符,每遇到一个字符就打印它到终端显示屏,直到遇到字符串结束符’\0’(这个’\0’不打印,即使打印你也看不到!)。
也就是说,实际上定义一个数组,如果不给他设置字符串结束符,那么计算机不仅仅会遍历到数组末尾 (你定义的字符,你虽然知道哪里是末尾,但是计算机不知道,它只管给你分配空间,不管你怎么去用),计算机它还会一直线性遍历数组末尾后面连续的字节空间,直到遇到第一个’\0’,但是实际上内存里面的’\0’非常多,所以往往很快就会遇到第一个字符串结束符’\0’,也就是很快结束字符串的输出。但是这个字符串和我们想要输出的字符串可不一样,因为内存里面是什么你不清楚,所以在输出完我们初始化的字符串的后面,那都是不确定的字符,也就会乱码。

解决方案

1.在数组末尾加一个’\0’,当然定义的14个空间要扩大一个字节变15

char str[15] = { ‘i’,’ ‘,‘a’,‘m’, ’ ‘,‘a’,’ ‘,‘s’,‘t’,‘u’,‘d’,‘e’,‘n’,‘t’,’\0’ };

2.定义数组的时候预留多一点空间,让计算机自动添加结束符

char str[15] = { ‘i’,’ ‘,‘a’,‘m’, ’ ‘,‘a’,’ ‘,‘s’,‘t’,‘u’,‘d’,‘e’,‘n’,‘t’ };
这次虽然没有在结尾添加’\0’,但是我预留了一个多余的空间,计算机看到这个缺缺会觉得非常难受,它就会把最不值钱的字符串结束符’\0’放进来
当然,没有必有这么节约空间
char str[100] = { ‘i’,’ ‘,‘a’,‘m’, ’ ‘,‘a’,’ ‘,‘s’,‘t’,‘u’,‘d’,‘e’,‘n’,‘t’ };
后面的所有多余空间都会补上’\0’

3.不定义数组空间,让系统自己分配,你只管初始化

char str[] = { ‘i’,’ ‘,‘a’,‘m’, ’ ‘,‘a’,’ ‘,‘s’,‘t’,‘u’,‘d’,‘e’,‘n’,‘t’ };
这样系统会在末尾补一个’\0’
char str[] = { ‘i’,’ ‘,‘a’,‘m’, ’ ‘,‘a’,’ ‘,‘s’,‘t’,‘u’,‘d’,‘e’,‘n’,‘t’ ,’\0’};
这样也可以

实际上用一个单引号初始化字符串很费力,直接用双引号也可以.

char str[100] =“i am a student!”; 字符串结束符会自动添加到后面,只是没有显示看到

2. sizeof和strlen()
sizeof运算符指出整个数组长度,而strlen()函数返回的是存储在数组的字符串的长度,而不是数组本身的长度,注意,strlen()函数不计算空字符。

char str[15] = { ‘i’,’ ',‘a’,‘m’, ’ ‘,‘a’,’ ',‘s’,‘t’,‘u’,‘d’,‘e’,‘n’,‘t’};
sizeof(str)=15,strlen(str)=14.

3.字符串输入
先看一个小程序

#include<iostream>
using namespace std;
const int ArSize = 20;
char name[ArSize];
char dessert[ArSize];
int main()
{
	cout <<" Enter your name : \n";
	cin >> name;
	cout << "Enter your favorite dessert:\n";
	cin >> dessert;
	cout << "I have some delicious " << dessert;
	cout << " for you, " << name << ".\n";
	return 0;

}

我们输入:Alistair Dreeb
看结果

Enter your name :
Alistair Dreeb
Enter your favorite dessert:
I have some delicious Dreeb for you, Alistair.

发现没有,我们还没有输入dessert,程序就运行完毕。
这是为什么呢?
这是因为,cin函数使用空白(空格,制表符,换行符)来确定字符串的结束位置,这意味着,cin读取完第一个字符串后遇到空白字符结束读取,然后把这个字符串放入数组,且在数组末尾补一个’\0’。
cin函数是在输入队列中取值并放入数组,它先检查输入缓冲区看看有没有字符,如果有,就将其提取出来直到遇到第一个空白,如果没有,那么输入缓冲区就等待用户输入。这个程序明显第二个cin发现输入队列还有字符,那么就不给用户输入的机会。

解决方案
通常,我们输出字符串都是由空格的,那么这个cin又不能有空格,那么我们也不可能每次都重新初始化数组吧,这样太麻烦。
我尝试了半天,发现方法都不理想,只要字符串有空格,制表符这些都会中断输入,

istream类提供了一些面向行的类成员函数:getline()和get().这两个函数都读取一行输入,直到到达换行符。然而,getline()将丢弃换行符,而get()将换行符保留下来。

1.getline()
getline()函数读取整行,它通过使用回车键输入的换行符来确定输入结尾。
调用cin.getline()。它有3个参数

basic_istream& __CLR_OR_THIS_CALL getline(_Elem* _Str, streamsize _Count)
basic_istream& __CLR_OR_THIS_CALL getline(_Elem* _Str, streamsize _Count, _Elem _Delim)

可以发现,getline函数有两个重载函数,第一个函数只有两个参数,这也是用的做多的。而第二个重载函数有三个参数。

1.第一个参数是用来存储输入行的数组的名称
2.第二个参数是要读取的字符数,注意要留一个空间给末尾自动补上的空字符(输入20,那么只能输入19个字符,最后一个给’\0’,系统会自己补上)
3.第三个参数是设定结束字符(系统默认为换行符’\n’)

#include<iostream>
using namespace std;
const int ArSize = 20;
char name[ArSize];
char dessert[ArSize];
int main()
{
	cin.getline(name,20);
	cout << name << endl;
	cin.getline(dessert, 20);
	cout << dessert << endl;
	return 0;

}

这个程序,当输入完一个字符串后按回车结束输入,getline()函数将字符串存储到数组,但是注意,换行符不会放进去,会被丢弃,即不再缓冲区也不在数组里面,然后数组末尾添加一个’\0’。

2.get()
get()函数有6个重载,这里不一一介绍,用的时候查阅一下就好了
cin.get()最多可以有三个参数cin.get(_Elem* _Str, streamsize _Count, _Elem _Delim)
第一个参数是放入读取字符串的数组,第二个参数是读入字符串的长度,注意保留一个位置存放’\0’,第三个参数是字符串结束符,如果不写,系统默认’\n’.这和cin.getline()是一样的.
cin.get()和cin.getline()长得很像,实际上接受的参数也一样,解释参数的方式也一样,都读取到行尾,但是注意,它不读取换行符,还是将换行符留着输入缓冲区.

这里不得不提一下用cin.getline和cin.get的区别了,有一个大大的不同哦

同时使用两次cin.getline和同时使用两次cin.get

会有区别吗?当然会,因为前面说到,cin.getline()读取换行符且丢弃,这样输入队列(缓冲区)里面就没有了换行符。这样的话,下一次再次调用cin.getline(),由于输入队列里面没有换行符了,那么系统会等待用户输入。这样很好。

但是cin.get()不会读取且丢掉换行符,这样会怎么样?第二次用cin.get()的时候,会发现输入队列有一个换行符,那么cin.get()函数认为到字符串末尾,将不再读取字符。这。。。。你一开始就结束,那下次还是这样,因为你换行符原地罚站,有点尴尬,这样会陷入一个死循环。

解决方法:

cin.get(name,20);
cin.get();
cin.get(dessert,20);

这个cin.get()的作用就是读取输入队列一个字符。注意,这个不带参数的get()是它的一个重载,不是所有函数都适用的。除法你自己定义了或着头文件的类包含了这个函数的重载。

除此之外,cin.get()和cin.getline()对于输入一个字符串的功能是相似的。
值得一提的是,除了使用cin.get()消除回车字符,还可以这样做:

cin.get(name,20).get();

这是因为,cin.get()函数返回一个cin对象,该对象随后将被用来调用cin函数。这个可能有点难理解,实际上,cin是istream类的一个对象,而cin.get()是一个调用函数,get()函数是istream类的成员函数。而get()返回值的类型是istream类,也就是get()函数结束函数进程,将返回一个istream类的对象,也就是说,如果将get()函数的类型改为void,那么就不能使用cin.get(name,20).get()这样高级的操作了。
同理,cin.getline(name,20).getline(name1,20)也是合理的。因为查看了一下源代码发现cin.getline()函数的返回类型也是istream.

疑难杂症
1.当cin.get(name.20)和cin.getline(name,20)读取空行的时候,将设置失效位(failbit),这意味着接下来的输入将被阻断,这时候就要用cin.clear()语句恢复环境。
也许读起来费解,给一个测试小程序

#include<iostream>
using namespace std;
const int ArSize = 20;
char name[ArSize];
char dessert[ArSize];
int main()
{
	cin.get(name,20);  //尝试直接输入换行
	//是不是发现直接退出程序了?下面的操作都被阻断
	//这个时候要用cin.clear()来恢复
	cin.clear();  //尝试不加这句话,看看和加了有什么区别
	cin.get();  //记得吃掉刚刚输入的回车
	cin.get(dessert,20);
	cout<<dessert;
	return 0;
	
}

2.
上面提到了如果用cin.get()和cin.getline()进行输入,那么一定要控制好字符串长度,首先:
必须预留一个字节存放’\0’,比如cin.get(name,20),那么只能输入19个字符,最后一个空间将由系统自动填充一个’\0’,代表字符串结束。
那么如果说,一不小心输入的字符串长度超过限制怎么办?首先,多余的字符不会放进数组,然后,getline()和get()将设置失效位,阻断后面的输入.

(2)c++风格的字符串

说实话啊,c风格的字符串,真的太难了,特别是学到c++的字符串,那真是省时间,省力气。❤️
ISO/ANSI C++98标准通过添加string类扩展了C++库,因此现在可以使用string类型的变量而不是字符数组来存储字符串。⭐️
string类位于名称空间std里面,所以要用的话要加上using namespace std;

首先,肯定的是,string能干的事情,通过字符数组也可以干。只是string将字符串的功能和特性归纳为在一起,在使用字符串的时候更加智能化
下面都是等价的:

char str1[20]=“i am a student!”;
string str2=“i am a student”;

cout< cout<

1.拼接,复制.
string类简化了字符串合并的操作,可以直接用+将两个string类对象接在一起。甚至可以用+=。毫无疑问这是对+的重载.

拼接

string str1=“i am a”;
string str2=" student!";
string str3=str1+str2;
str1+=str2;
输出str3,是i am a student!
输出str1,是i am a student!

赋值

str1=str2;
cout< 输出 student!

2.strlen().
string类提供两种函数计算字符串里面的字符个数;

  1. size()
  2. length()

通过一个小程序来说明两个函数的用法

#include<iostream>
using namespace std;
#include<cstring>

int main()
{
	string str1="i am a boy!";
	string str2="i am a girl!";
	int len1=str1.size();

> 这里是引用

	int len2=str2.size();
	//int len1=str1.length();  //结果和上面一样的
	//int len2=str2.length();
	cout<<len1<<endl;
	cout<<len2<<endl;
	return 0;
}

那么有疑问了,这两个函数有什么区别呢?
我查了一下源码;

_NODISCARD _CONSTEXPR20 size_type length() const noexcept {
return _Mypair._Myval2._Mysize;
}
_NODISCARD _CONSTEXPR20 size_type size() const noexcept {
return _Mypair._Myval2._Mysize;
}

可以发现这不是说很像,只能说一模一样。
实际上还有第三种方法,就是使用strlen()函数

size_t __cdecl strlen(
In_z char const* _Str
);

很可惜,strlen()函数只能接受一个指向字符常量的指针,没有从std::string到const char*的转换函数。那么既然不能转换,那就强制转换一下。

const char *p=str1.c_str();

说明一下,string类的函数c_str()返回string字符串的首地址,且类型为const char*

#include<iostream>
	using namespace std;
#include<cstring>

	int main()
	{
		string str1 = "i am a boy!";
		string str2 = "i am a girl!";
		const char* p = str1.c_str();
		cout << p << endl;
		p = str2.c_str();
		cout << p;
		cout << endl << strlen(p);
		}

3.string的各种常用函数.
1.insert(pos,string)

string str1=“abc”;
string str2=“abc”;
str1.insert(1,str2);
cout< 输出:aabcbc

这说明insert里面的1是指从下标1开始进行插入,后面的元素自动向右移动。很方便。

2.insert(it,it1,it2)
it指的是插入的位置,用迭代器表示。
it1,it2是要放入的字符串的区间,下面用例子说明

using namespace std;
int main()
{
string s=“abcefgh”;
string s1=“xxxxx”;
s.insert(s.begin()+1,s1.begin(),s1.end());
cout< return 0;
}
输出:axxxxxbcefgh

3.erase(it)
这是删除函数,也是通过迭代器指针删除指定元素

using namespace std;
int main()
{
string s=“abcefgh”;
s.erase(s.begin()+3);
cout< return 0;
}
输出:abcfgh

4.erase(it1,it2)
it1表示删除的左边界,it2表示删除的有边界

using namespace std;
int main()
{
string s=“abcefghij”;
s.erase(s.begin()+3,s.begin()+6);//h是第六个位置,但是是开区间,不能删除
cout< return 0;
}
输出:abchij

5.substr(pos,len)
这是截取字符串函数

using namespace std;
int main()
{
string s=“abcefghij”;
string s1=s.substr(0,4);//注意是从第0个位置上长度为4的字符,数长度的时候应该从1开始
cout< return 0;}
输出:abcd

6.find(string)
这是查找子字符串函数,如果找到,返回其第一次出现的位置,如果没有找到,返回-1

using namespace std;
int main() {
string s=“abcefghij”;
int pos=s.find(“be”);
int pos1=s.find(“ce”);
cout< return 0;
}
输出:-1 2

7.find(string,pos)
意思是从pos位置开始找,找到第一个为string的字符串,找到返回他的地址,没有找到返回-1

using namespace std;
int main()
{
string s=“abcefghijce”;
int pos=s.find(“ce”);
int pos1=s.find(“ce”,pos);//这里pos1还是等于2,因为pos的值是2,相当于从第二位开始找,一下就找到了,所以值不变
int pos2=s.find(“ce”,pos+1);//这里pos2就是9了,因为是从pos+1,也就是第三位开始找
cout< return 0;
}
输出:
2 2 9

8.replace(pos,len,string)
pos是起始位置,len代表长度,string是要替换的字符串

using namespace std;
int main()
{
string s=“abcdttefghtt”;
int pos=s.find(“tt”);//pos等于4
s.replace(pos,2,“pppp”);//从第四位开始的长度为2的子串替换为pppp
cout< return 0;
}
输出:abcdppppefghtt

2.共用体(union)和枚举(enum)

(1)共用体(union)

定义:
共用体是一种数据格式,它能够存储不同类型的数据类型,但只能同时存储其中的一种类型。也即是说,结构可以同时存储int ,long,double.而共用体只能存储int或者doule或者long。他和结构体很像,但又完全不同.

union one4all
{
	int int_val;
	long long_val;
	double double_val;
};

上面是一个共用体,它的名称是one4all,它可以用来存储int,long,double类型的数据,但是有一个前提,就是,一次只能存储一个类型。比如说,它存储int,就不能存储double

one4all pail;
pail.int_val=15;
cout<<pail.int_val;
pail.double_val=1.39;
cout<<pail.double_val;

它的用法,或者说有什么意义?
当数据项使用两种或两种以上的类型(但是又保证不会同时使用)时,用共用体可以节约空间。例如:假设管理一个小商品目录,其中有一些商品的ID为整数,而另一些的ID为字符串。在这种情况下,可以这样做:

struct widget
{
char brand[20];
int type;
union id
{
	long id_num;
	char id_char[20];
}id_val;
};
widget prize;

if(prize.type==1)
	cin>>prize.id_val.id_num;
else
	cin>>prize.id_val,id_char;

由此可以看出共用体和结构体的区别,共用体变量只能在它包含的数据类型中选择一个作为自己唯一的类型,而结构体变量同时存储多个类型.

用途:
虽然说着节省的空间非常少,但是考虑到一些硬件的技术和需要,能节省一点空间也是很好的。比如漫步者耳机,MP3等等…

(2) 枚举(enum)

C++的enum工具提供一种方便的方式创建符号常量,这种方式可以代替const。它还运行定义新类型,但是必须按严格的限制完成。

enum spectrum{red, orange, yellow, green, blue};

上面这条语句的意思是:

  1. 让spectrum成为新类型的名称;
  2. 其中的red,blue这些都变成了符号常量,等价于const red,const blue。
  3. 如果不显示的赋值,那么这些符号常量的初始值从0开始,依次增加1(red=0,orange=1,yellow=2,green=3,blue=4)

设置枚举的值
1.初始化的时候赋值

enum spectrum{red=100,orange,yellow=40,green,blue=0};

  1. red的值为100,yellow的值为40,blue的值为0
  2. 没有显示初始化的是多少呢?还是严格按照默认的递增加1规则。那么,orange=101,green=41.

2.直接赋值
如果直接赋值,那么又要严格遵守枚举范围。
取值范围:

首先,找到枚举量的最大值,然后找到大于这个最大值的2的幂,然后减1.比如上面枚举最大值是100,那么枚举赋值的范围上限是128-1=127;
然后,找到枚举量的最小值,如果不小于0,则下限为0,否则,采用与寻找上限相同的方式,最后加上负号即可。

找到枚举范围,然后就可以用强制转换类型转换赋值了。

enum bits{one =1,two=2,four=4,eight=8};
bits ff;
ff=bits(6) //那么ff的值就是6,而6在枚举范围内,所以成立
ff=bits(9) //不成立

注意;ff=9,像这种赋值是不允许的,不存在有int型转换为bits型的转换函数,所以要用强制转换规则赋值

3.指针

指针在之前的博客写了,就不多加复习,挑一些之前不注意的地方复习一下

1.指针初始化值

int *p;
*p=1234;

如果没有对p进行初始化,那么p的值是随机的,也就是说,p指向的内容可能是啥也没有,也有可能是有数值的,因为p是随机的,也即是地址是随机的。那么这样会造成数据丢失,可能使程序有一些难以解决的bug。
所以一定要对p进行初始化;

int *p=NULL;
int a=1234;
p=&a;

2.new运算符

在C里面,我们通常用malloc分配内存空间。在C++里,增加了新的分配方式,new运算符.

int *p=new int;

意思是,让p指向一个int型的内存空间。new运算符根据类型来确定需要多少字节的内存。然后,它找到这样的内存,并返回其地址。接下来,将地址赋给p,所以p就是地址,*p就是地址存储的值。

动态创建数组
分配一个数组空间,大小为10个int

int *psome=new int [10]

同时,要用delete释放

delete [] psome;

3.指针算术

#include<iostream>
using namespace std;
int main()
{
	double wages[3]={10000.0,20000.0,30000.0};
	short stacks[3]={3,2,1};
	double *pw=wages;   //将数组的地址给pw
	short *ps=&stacks[0];   //同理
	cout<<"pw= "<<pw<<", *pw= "<<endl;
	pw=pw+1;   //注意这个+1是什么意思
	cout<<"add 1 to pw:\n";
	cout<<"pw= "<<pw<<", *pw= "<<*pw<<endl;
	cout<<"ps= "<<ps<<", *ps="<<*ps<<endl;
	ps=ps+1;
	cout<<"add 1 to the ps:\n";
	cout<<"ps= "<<ps<<", *ps= "<<*ps<<"\n\n";
	return 0;
	
}

上面的程序通过对指针+1,发现,实际上并不是地址简单的加1,而是加了8个字节,说明,在这里指针不是整型,虽然可以用整型相加,但是这个加数代表的意思不是单纯的int类型,应该代表的是“1个单位的指针空间”,在这里就是8个字节,一个单位double的大小。

4.数组的地址
有意思的是,在我们对数组取地址的时候,数组名不会被解释为其地址。
比如:

int tell[10];
cout<<tell<<endl;
cout<<&tell<<endl;

从结果来说,这两个值是一样的。但是从概念上来说,他们却截然不同!
&tell[0]也就是tell 是一个4个字节的内存块的地址。而&tell是一个40个字节的内存块的地址。显而易见,表达式tell+1将地址加4,表达式&tell+1将地址加40。相当于,tell是一个int型的指针,而&tell是一个int()[10]型的指针。

好了,今天的学习,到这里。明天继续复习C++。

你可能感兴趣的:(c++prime,plus,c++,开发语言)