C++ Primer Plus 第4章

第4章 复合类型

4.1 数组

①sizeof 使用方法有两种
方法一:sizeof(----)
方法二:sizeof ---- 无括号
②初始化数组时,可不在大括号内包含任何东西,就可以把所有元素设置为0
int a[10] = {} (甚至等号都可以省略)
③数组的声明
typeName array[arraySize]
arraySize必须是整型常数或const值,也可以是常量表达式,但不能是变量,变量的值是在运行时设置的,而数组的空间是在编译阶段就分配的
④两个数组不能直接赋值
③C++数组初始化的一种方式

4.2 字符串

4.2.1 字符串初始化

char  s[100] = "Hello World!";  //之后的所有空间都被初始化为\0

4.2.2 拼接字符串常量

cout << "I love you";
cout << "I love "     "you";

是等价的,第一个字符串中的\0字符将被第二个字符串中的第一个字符替换

4.2.3 字符串输入
①cin使用空白符来确定字符串的结束位置,如果字符串开头就是空白符,cin会忽略它们
②istream的类成员函数:getline()和get()cin
这两个函数都读取一行输入,直到达到换行符。然而,getline将读取换行符并丢弃,get则将换行符留在缓冲区,无法继续使用它读取
a.getline()
调用方法:cin.getline(数组名,要读取的字符数)
如果要读取的字符数是20,则最多能读到19个字符,因为还要在结尾加上\0
getline在读取到指定数目的字符或遇到换行符时停止读取
b.get()
这个名为get的成员函数有多种变体,其中一种和getline功能类似,区别在于它不读取缓冲区的换行符,如果不借助于帮助,get将不能跨过该换行符

cin.get(数组名,要读取的字符数);

变体:

cin.get();  可以读取下一个字符,无论该字符是什么

还要知道的一点是,cin.get(数组名,要读取的字符数)和cin.getline(数组名,要读取的字符数)的返回值仍是cin类,所以可以继续调用成员函数,如

cin.getline(array1, 20).getline(array2, 20);
cin.get(name, 20).get();

通过get函数及其变体我们可以揣测到,C++函数允许有多个版本。条件是这些版本的参数列表不同,这就是我之后要学习的------函数重载

下面来对比一下get和getline
getline的输入更简单,而get的输入更精确,因为使用getline读入结束时,不清楚是由于遇到了空字符,还是读到了指定数目的字符,而get可以,具体怎么个可以法,要在第17章学习

4.3 string类简介

4.3.1 string类的声明和初始化
①string类位于名称空间std中,使用前请调用using namespce std
string str1;
str1的声明,创建了一个长度为0的string对象,当程序将输入读取到str1中时,将自动调整str1的长度

4.3.2 string类的厉害操作
①可以将一个string对象赋给另一个string对象
②可以使用+运算符把两个string对象合并起来
③可以使用+=运算符把字符串附加到string对象的尾部
④str1.size()返回值是字符串长度(当然不包括\0)
⑤string类具有自动调整大小的功能,所以可以避免C语言中为字符串数组赋值而越界的现象

4.3.3 string类I/O
①使用字符串数组表示字符串时,如果未对数组初始化,数组中字符串的长度是不确定的,还可能大于字符串长度,因为程序会从首地址开始找\0,在找到之前读到的几个字符,返回字符串大小是几
getlnie(cin, str)
str是个string类对象,这个引用getline并没有使用成员运算符,说明这里的getline是个函数,而不是类方法,它将cin作为参数,指出到哪里去查找输入,然后将输入读取到str中
为什么处理字符串的getline没有成为类方法呢?这是个历史问题。因为在C++引入string类之前,C++就有istream类,在istream类中没有考虑string类,所以这里的getline是函数,而非类方法

4.3.4 奇奇怪怪的原始字符串
C++新增的另一种类型是原始字符串,即字符表示的就是自己本身,如\n不表示换行符,就表示两个字符\和n,所以也可以在字符串中包含"了,那就不能用双引号来表示字符串了,因为会出现歧义,我们可以使用标准的定界符"(字符串)",并使用R作为前缀来表示原始字符串,如

cout << R"(I am the "KING" of myself!)" << "\n";

如果我想输出的字符串中出现"(,那就需要自定义定界符,方法就是在前后的"和(之间都加入任意个字符,但要保证前后加入的字符数目和顺序一致,如下面的代码

cout << R"+++(I am the "KING" of myself!)+++" << "\n";

4.4 结构简介

①C++ 允许在声明结构体时省略关键字struct
②C++结构初始化

#inlcude <string>
struct inflatable
{
	char name[];
	float volume;
	double price;
};

inflatable  duck = {};
这种声明方式,即大括号里没有任何东西,会把各个成员都设置为0,name数组的每一个个元素都被设置为\0,volume和price都被设置为0

③两个同类型的结构体对象可以相互赋值,即使成员中有数组

4.5 共用体

①共用体一个时刻只能存储其中的一个类型,共用体的长度为其最大成员的长度
②共用体的使用实例

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

	widget prize = {"NIKE", 1};
	if (prize.type == 1)
		cin >> prize.id_val.id_num;
	else
		cin >> prize.id_val.id_char;

③匿名共用体
匿名共用体没有名称,其成员将成为位于相同地址处的变量

struct widget
	{
		char band[20];
		int type;
		union 
		{
			long id_num;
			char id_char[20];
		};
	};

	widget prize = {"NIKE", 1};
	if (prize.type == 1)
		cin >> prize.id_num;
	else
		cin >> prize.id_char;  //由于共用体是匿名的,因此id_num和id_char被视为prize的两个成员,地址相同,不需要中间标识符

4.6 枚举

①C++的enum工具提供了另一种创建符号常量的方法,即枚举类型
②两种常用的声明方法
方法一:enum spectrum {red, orange, yellow, green, blue, violet, indigo};
方法二:enum {red, orange, yellow, green, blue, violet, indigo};
方法一将soectrum声明为一个枚举对象(一个类型),可以用来声明枚举变量,比如
spectrum band;
方法二用于,只打算使用枚举常量,而不打算创建枚举型的变量的情况
枚举类型的特殊之处在于,在不进行强转的情况下,枚举变量只能由已声明的符号常量来为他赋值
③枚举量是整型,可以被提升为Int型,如

int color  = blue;  //blue先自动提升为int型,再给整型color赋值
color = blue + 3;   //blue先自动提升为int型,再与3相加,再给整型color赋值

“枚举只定义了赋值运算符,没有定义其他与算符的理解”

spectrum band;
band = blue + red;   不合法的原因其实是,在算术表达式中,枚举被转换为int型,故blue+red是合法的整数相加操作,错就错在不能吧整型赋值给一个枚举变量

④使用强转来为枚举变量赋值
band = spectrum(3);

⑤可以随意设置枚举量的值,未设置值的枚举量从有值开始依次加一,如果第一个枚举量没有设置值,默认为0

⑥强制类型转换扩大了枚举变量赋值的范围
每个枚举都有取值范围,通过强制类型转换,可以将取值范围中的任何整数值赋给枚举变量
最大值是大于最大值的最小的2的幂-1
最小值:如果枚举量都不小于0,那最小值就是0
否则,最小值先去绝对值,然后按求最大值的方法求完加负号就行

4.7 指针和自由存储空间

①面向对象编程与传统的过程性编程的区别在于,OOP强调的是在运行阶段而非编译阶段进行决策
②使用new来分配内存
前导知识:
和C类似,在函数中,变量地址等都在栈中存储,退出函数,则栈空间释放,而堆空间在函数结束后仍保留,在C中是malloc和free两个函数管理者堆空间,到了C++,是new和delet管理着堆空间

int *pn = new int;   pn是个指针,它指向的是一个内存空间的首地址
通用的声明格式为
TypeName * pointer_name = new TypeName;
delete  pn;  delete只会删除内存空间,并不会删除指针,delete将删除的内存空间还给内存池

不能使用delete来释放声明变量所获得的的内存,一个是管理堆的,一个是栈内存,不是一回事
对空指针使用delete是安全的
③使用new来创建动态数组
静态联编:在编译时给数组分配内存
动态联编:在程序运行时选择数组长度
使用new来创建动态数组:

int * psome = new int [10];  new运算符返回第一个元素的地址、
delete  [] psome;  方括号告诉程序,应释放整个数组,而不仅仅是指针指向的元素

为数组分配内存的通用格式如下

type_name * pointer_name = new type_name [num_elements];

④不能使用sizeof运算符来确定动态分配的数组包含的字节数
⑤指针的值是可以修改的,数组名虽也是地址,但他是个常量,无法修改,注意,一定不要delete修改后的已分配堆空间的指针,必须让他指向最初分配时的首地址才行,否则程序会崩溃,感兴趣的可以试试

#include <iostream>
using namespace std;
int main()
{
	double* p3 = new double[3];
	p3[0] = 0.1;
	p3[1] = 0.2;
	p3[2] = 0.3;
	double* p2;
	p2 = p3;
	p3--;
	delete [] p3;  在这里就崩溃了
	cout << p2[0];
}

⑥数组名本身就代表了第一个元素的地址,因此取地址符号+数组名,表示的并不是第一个元素的地址,而是整个数组的的地址,也就是将整个一维数组作为一个内存块来看待,即整个数组对于&array的地位,相当于数组的第一个元素对于array的地位

⑦指针和字符串
下面我们来探究一下字符串的输出cout
cout << 后面可以跟一个双引号括起来的字符串,也可以跟一个字符型变量
其实cout对于这两种形式的处理是一样的,因为双引号括起来的字符串,本质上也是这个字符串第一个元素的地址,cout获得了一个字符串的第一个元素的地址后,会开始输出字符串,知道遇到\0
那么如何打印一个字符串的地址呢?
方法是先将这个指针强转为指向int的指针,比如

char *name[10];
cout << (int *)name << endl;

⑧使用strcpy时的注意
如果该函数在到达字符串结尾之前,目标内存已经用完,则他将不会添加空字符,因此,为了保证复制的内容仍为字符串,可以做这样的处理(但一样没有string类方便)

char food[20];
strcpy(food, "a picnic basket filled with many goodies", 19);
food[19] = '\0';

⑨使用new来创建动态结构
如果结构标识符是结构名,则使用句点运算符来访问成员;如果标识符是指针,则使用箭头运算符来访问成员

⑩自动存储、静态存储和动态存储(也叫自由存储空间或堆)
自动存储通常存储在栈中,自动存储的变量的生存周期,仅仅是当前内存块,
静态存储的生存周期是整个程序执行期间,静态变量的声明,一种是在函数外面声明,另一种是在函数内部,加上static声明
动态存储的生存周期,完全不受程序或函数的限制

4.8 类型组合

仅列举有点点复杂的数组指针和666的auto

struct  Date
{
	int year;
	int month;
	int day;
}
Date date[10];
const Date ** pp = date;   //意思是pp是第一个元素的地址的地址 

4.10 数组的替代品

①模板类vector
类似于string,vector也是一种动态数组,可以在运行阶段设置vector的长度,可以在末尾添加数据,可以在中间插入新数据。基本上,它是使用new创建动态数组的替代品。实际上,vector类确实是使用new和delete来管理内存,但这种工作是自动完成的
使用注意:
要使用vector对象,必须包含vector头文件
vector包含在std名称空间中
关于vector对象的两种声明

#include <vector>
using namespace std;
vector<int> vi;
int n;
cin >> n;
vector<double> vd<n>;  vector初始长度为0,可以用常量或变量来设置初始长度,

②模板类array(C++11)
与数组一样,array对象的长度是固定的,也是用栈存储,因此其效率与数组相同,但更方便,更安全
使用时需要array头文件
声明如下

#include <array>;
using namespace std;
array<int, 5> ai;
array<double, n_elem> ad = {1.2, 1.2, 1.2, 2.1, 2.1}; 
n_elem不能是变量!!

③数组,vector对象和array对象,都可以使用标准数组表示法来访问各个元素
长度相等的array对象之间可以相互赋值

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值