C++详细学习笔记(三)

今天要学习的是C++中的复合类型,包括以下几个内容:

1、创建和使用数组。

2、创建和使用C-风格字符串。

3、创建和使用string类字符串。

4、使用方法getline()和get()读取字符串。

5、混合输入字符串和数字。

6、创建和使用结构体。

7、创建和使用共用体。

8、创建和使用枚举。

9、创建和使用指针。

10、使用new和delete管理动态内存。

11、创建动态数组。

12、创建动态结构。

13、自动存储、静态存储和动态存储。

14、Vector和array类简介。

一、数组

使用数组,首先要声明数组:typeName arrayName[arraysize];访问数组元素可以使用带索引的方括号表示法来指定数组元素。

有效下标的重要性:

编译器不会检查使用的下标是否有效,但是程序运行后,这种赋值可能引发问题,它可能破坏数据或代码,也可能导致程序异常终止。

1.1数组的初始化规则

只有在定义数组时才能初始化,此后就不能使用了,也不能将一个数组赋给另一个数组:

Int cards[4]={3,6,8,10};  //okay

Int hand[4]; //okay

Hand[4]={5,6,7,9};//not allowed

Hand=cards;//not allowed

1.2 C++11数组初始化方法

C++11的列表初始化新增了一些功能

(1)初始化数组时,可省略等号(=):
double earnings[4] {1.2e4,1.6e4,1.1e4,1.7e4};

(2)可不在大括号内包含任何东西,这将把所有的元素都设置为零。

(3)列表初始化禁止缩窄转换。

二、字符串

C++处理字符串的方式有两种。第一种来自C语言,常称为C-风格字符串,另一种是基于string类库的方法。

C-风格字符串具有一种特殊的性质:以空字符结尾,空字符被写作\0,其ASCII码为0,用来标记字符串的结尾。

字符串常量可以用来初始化字符数组,但要区别于字符常量。

2.1拼接字符串常量

有时候字符串很长,无法放到一行中。C++允许拼接字符串字面值,即将两个用引号括起的字符串合并为一个。事实上,任何两个由空白(空格、制表符和换行符)分隔的字符串常量都将会自动拼接成一个。

注意,拼接时不会在被连接的字符串之间添加空格,第二个字符串的第一个字符将紧跟在第一个字符串的最后一个字符后面。第一个字符串中的\0字符将被第二个字符串中的第一个字符取代。

2.2在数组中使用字符串

   要将字符串存储到数组中,最常见两种方法——将数组初始化为字符串常量、将键盘或文件输入读入到数组中。

   可以使用标准C语言库函数strlen()来确定字符串长度。标准头文件cstring提供了该函数以及很多字符串相关的基本函数声明。但sizeof()运算符和Strlen()函数是有区别的,前者指出了整个数组的长度,后者函数返回的是存储在数组中的字符串长度,而不是数组本身长度,且其只计算可见的字符,而不把空字符计算在内。

2.3字符串的输入和每次读取一行字符串的输入

Cin使用空白来确定字符串的结束位置,这意味着cin在获取字符数组输入时只读取一个单词。读取该单词后,cin将该字符串放到数组中,并自动在结尾添加空字符。

而有时需要将整条短语而不是一个单词作为字符串输入时,需要采用面向行而不是面向单词的方法。可用isteam中类提供的一些面向行的类成员函数:getline()和get()。这两种函数都读取一行输入,直到到达换行符。然而,随后getline()将换行符丢弃,而get()将换行符保留在输入序列中。

(1)面向行的输入getline()

      getline()函数读取整行,它通过回车键输入的换行符来确定输入结尾。该函数有两个参数:cin.getline(name,20);

      getline()函数每次读取一行,它通过换行符来确定行尾,但不保存换行符,相反,在存储字符串时,它用空字符来替换换行符。

(2)面向行的输入get()

      该函数有几种变体。

1、类似于getline(),它们接受的参数相同,解释参数的方式也相同,并且都读取到行尾。但get并不再读取并并丢换行符,而是将其留在输入队列中。如果两次调用get(),由于第一次调用后,换行符保留在输入队列中,因此第二次调用时看到的第一个字符便是换行符。因此get()认为已达到行尾,而没有发现任何可读的内容,如果不借助于帮助,get()将不能跨过该换行符。幸运的是,get()有另一种变体,使用不带任何参数的cin.get()调用可读取下一个字符,即便是换行符,为读取下一行输入做好准备。

2、将两种类成员函数拼接起来(合并),如下:cin.get(name,20).get()。之所以这样做,是由于cin.get(name,20)返回一个cin对象,该对象随后将被用来调用get()函数。

之所以使用get()函数,首先,老式实现没有getline(),其次,get()使输入更仔细。例如,用get()将一行读入数组中,如何知道停止读取的原因是因为由于已经读取了整行,而不是由于数组已经填满?查看下一个输入字符,如果是换行符,说明已经读取整行;否则,说明该行中还有其他输入。

2.4空行和其他问题

getline()或get()读取空行时,将会发生什么?最初的做法是,下一条输入语句将在前一条getline()或get()结束读取的位置开始读取;但当前的做法是,当get()(不是getline())读取空行后设置失效位(failbit)。这意味着接下来的输入将被阻断,但可以用下面的命令来恢复输入:cin.clear();

另一个潜在问题是,输入字符串可能比分配的空间长。如果输入行包含的字符数比指定的多,则getline()和get()将余下的字符留在输入队列中,而getline()还会设置失效位,并关闭后面的输入。

2.5混合输入字符串和数字。

三、String类简介

 ISO/ANSIC++98标准通过添加string类扩展了C++库,因此可以string类型的变量(使用C++的话说对象)而不是字符数组来存储字符串。String类使用起来比数组简单,同时提供了将字符串作为一种数据类型的表示方法。

要使用string类,必须在程序中包含头文件string。String类位于名称空间std中,因此必须提供一条using编译指令,或者使用std::string来引用它。String类定义隐藏了字符串的数组性质,能够像处理变量那样处理字符串。

String对象和字符数组之间的主要区别是,可以将string对象声明为简单变量,而不是数组。类设计让程序能够自动处理string的大小。从理论上说,可以将char数组视为一组用于存储一个字符串的char存储单元,而string类变量是一个表示字符串的实体。

3.1赋值、拼接和附加

使用string类时,某些操作比使用数组时更为简单。例如,不能将一个数组赋给另一个数组,但可以将一个string对象赋给另一个string对象。

String类简化了字符串合并操作,可以使用运算符+将两个string对象合并起来,还可以使用运算符+=将字符串附加到string对象的末尾。

3.2string类的其他操作

C++新增string类之前,要完成诸如给字符串赋值等工作,对于C-风格字符串来说,使用C语言库中的函数来完成这些任务。头文件cstring提供了这些函数。

3.3string类I/O

要用函数strlen()来计算空数组的字节数时,得到的数组长度可能是与定义的数组长度是不同的。因为未初始化的数组的内容是未定义的,其次,函数strlen()从数组的第一个元素开始计算字节数,直到遇到空字符。对于未被初始化的数据,第一个空字符的出现位置是随机的。

而对于未初始化的string对象来说,其长度是被自动设置为0的,所以str.size()是等于0的。

还要区别cin.getline(Charr,20);和语句getline(cin,str);

对于前者来说,函数getline()是istream类对象cin的成员函数,第一个参数是目标数组,第二个参数是数组长度,用来避免超越数组的边界。而对于后者来说,这里没有使用句点,说明这个getline()不是类方法。它将cin作为参数,指出到哪里取查找输入,另外也没有指出字符串长度的参数,因为string对象根据字符串长度自动调整自己的大小。

而这是为什么呢?

在引入string类之前很久,C++就有istream类。因为istream类考虑到了诸如double和int等基本数据类型,但没有考虑到string类型,所以istream类中,有处理double、int和其他基本类型的方法,而没有处理string对象的类方法。

四、结构

结构是用户定义的类型,而结构的声明定义了这种类型的数据属性。定义了类型后,便可以创建这种类型的变量。因此创建结构包括两步。首先,定义结构描述——它描述并标记了能够存储在结构中的各种数据类型。然后按描述创建结构变量。

C++中的创建结构体变量与C语言不同,C++可以省略掉关键字struct,而不出错。

C++不提倡在使用外部变量,但体长使用外部结构,在外部声明符号常量通常更合理。

C语言一样,C++也允许指定占用特定位数的结构成员,这使得创建于与某个硬件设备上的寄存器对应的数据结构非常方便。字段的类型应为整型或枚举,接下来是冒号,冒号后面是一个数字,它指定了使用的位数。可以使用没有名称的字段来提供间距。每个成员都被为位字段。如下:

Struct torgle_register

{

Unsigned int SN:4;

Unsigned int :4;

Bool goodIn:1;

Bool goodTorgle:1;

};

同时可以像通常那样初始化这些字段,还可以使用标准的结构表示法来访问这些字段。

五、共用体

共用体是一种数据结构,它能够存储不同的数据类型,但只能同时存储其中一种类型。也就是说,结构可以同时存储int、long和double,共用体只能存储int、long或double。共用体的句法与结构相似,但含义不同。

Union one4all

{

Int int_val;

Long long_val;

Double double_val;

}

可以使用one4all变量来存储int、long或double,条件是在不同的时间进行。

One4all pail;

Pail.int_val=15;

Cout<<pail.int_val;

Pail.double_val=1,38;

Cout<<pail.double_val;

因为pail有时可以是int变量,有时又可以是double变量。成员名称标识了变量的容量。由于共用体每次只能存储一个值,因此它必须有足够的空间来存储最大的成员,所以共用体的长度为其最大成员的长度。

共用体的用途之一,当数据项使用两种或更多格式时(但不会同时使用),可以节省空间。另外,共用体常用于操作系统数据结构或硬件数据结构。

六、枚举

C++的enum工具提供了另一种创建符号常量的方式,这种方式可以替代const。它还允许定义新的类型,但必须按照严格的限制进行。使用enum的句法与结构相似。

enum  spectrum{red,orange,yellow,green,blue,violet,indigo,ultraviolet};

spectrum被称为枚举,red、orange等作为符号常量,它们对应整数0-7.这些常量叫做枚举量。

枚举变量有一些特殊的属性,如下:

在不进行强制转换的情况下,只能将定义枚举时使用的枚举量赋给这种枚举的变量。如:band=blue;但不能band=2000;因此,spectrum变量受到限制,只有8个可能的值,视图将一个非法的值赋给它,则有些编译器将出现编译错误,而另一些则发出警告。

对于枚举,只定义了赋值运算符,没有定义算术运算。

枚举量是整型,可被提升为int类型,但int类型不能自动转换为枚举类型。

Band=3是错误的,color=3+red;

但对于band=orange+red;又是错误的,虽然orange+red可看成是1+0,但是其结果类型是int,不能赋值给枚举类型。

枚举更常被用来定义相关的符号常量,而不是新类型。

6.1设置枚举量的值

后面没有被初始化的枚举量的值将比前面的枚举量大1。早期版本中,只能将int值赋给枚举量,但这种限制取消了,因此可以使用long甚至long long类型的值

6.2枚举的取值范围

最初,对于枚举来说,只有声明中的那些值是有效的。然而,C++现在通过强制转换类型,增加了可赋给枚举变量的合法值。每个枚举都有取值范围,通过强制类型转换,可将取值范围中的任意整数赋值给枚举变量,即使这个值不是枚举值。

取值范围的定义如下:

首先,要找到上限,需要知道枚举量的最大值。找到大于这个最大值的并是最小的2的幂,将它减去1,得到的便是取值范围的上限。要计算下限,需要知道枚举量的最小值。如果它不小于0,则取值范围的下限为0;否则,采用与寻找上限方式相同的方式,但加上负号,如果最小的枚举量为-6,而比它小的、最大的2的幂是-8,因此下限为-7.

选择用多少空间来存储枚举由编译器决定。对于取值范围较小的枚举,使用一个字节或更少的空间,而对于包含long类型值得枚举,则使用4个字节。

七、指针和自由存储空间

极其重要的一点:在C++中创建指针时,计算机将分配用来存储地址的内存,但不会分配用来存储指针的所指向的数据的内存。为数据提供空间是一个独立的步骤,忽略这一步无疑是自找麻烦,如下所示:

Long* fellow;

*fellow=223323;

上述代码并没有将地址赋给fellow,那么223323被放到哪就不知道了。由于fellow没有被初始化,它可能有任何值。不管值是什么,程序都将它解释为存储223323的地址。如果fellow的值碰巧为1200,计算机把数据放到地址1200上,即使着恰巧是程序代码的地址。Fellow指向的地方很可能并不是所要存储223323的地方。这种错误可能会导致一些最隐匿、最难以跟踪的bug。

一定要在对指针应用解除引用运算符(*)之前,将指针初始化为一个确定的、适当的地址。

7.1使用new来分配内存

前面我们都将指针初始化为变量的地址:变量是在编译时分配的有名称的内存,而指针只是为通过名称直接访问的内存提供了一个别名.指针真正的用武之地在于,在运行阶段分配未命名的内存以存储值.

new运算符就是在运行阶段为一个int值分配未命名的内存,并用指针来访问这个值.程序员要告诉new,需要未哪种数据类型分配内存;new将找到一个长度正确的内存块,并返回该内存块的地址。程序员的责任就是将该地址赋给一个指针。

如:int* pn=new int;

new int告诉程序,需要适合存储int的内存。New运算符根据类型来确定需要多少字节的内存,并返回地址,赋给pn,pn被声明为指向int的指针。

7.2用delete来释放内存

delete运算符,它使得在使用完内存后,能够将其归还给内存池。使用delete时,后面加上指向内存块的指针(这些内存块最初使用new分配的)。

如:delete pn;

这将释放pn指向的内存,但不会删除指针pn本身。例如可以将pn重新指向另一个新分配的内存块,一定要配合使用new和delete,否则将发生内存泄漏,也就是说,被分配的内存将再也无法使用了,程序将由于不断寻找更多内存而终止。

不要尝试释放已经释放的内存块,C++标准指出,这样做的结果是不确定的,这意味着什么情况都可能发生,另外,不能使用delete来释放声明变量所获得的内存。然而对空指针使用delete是安全的。

注意,使用delete的关键在于,将它用于new分配的内存。这并不是意味着要使用用于new的指针,而是用于new的地址。一般来说,不要创建两个指向同一个内存块的指针,因为这将增加错误地删除同一个内存块两次地可能性。

7.3使用new来创建动态数组

在编译时给数组分配内存被称为静态联编,意味着数组在编译时加入到程序中。但使用new,如果在运行阶段需要数组,则创建它;如果不需要,则不创建。还可以在程序运行时选择数组长度,这称为动态联编,意味着数组是在程序运行时创建的。这种数组叫做动态数组。使用静态联编,必须在编写程序时指定数组的长度;使用动态联编时,程序将在运行时确定数组的长度。

创建动态数组,需要将数组的元素类型和元素数目告诉new即可,

int* psome=new int[10];

new运算符将返回第一个元素的地址。

释放内存为 delete [] psome;

方括号告诉程序应释放整个数组,而不仅仅是指针指向的元素。

实际上,程序跟踪了分配的内存量,以便以后使用delete []运算符时能够正确地释放这些内存。但这种信息是不公用地,例如,不能使用sizeof运算符来确定动态分配的数组包含的字节数。

八、指针、数组和指针运算  

sizeof运算符用于数组名时,此时将返回整个数组的长度。

8.1指针和字符串

数组和指针的特殊关系可以扩展到C-风格字符串,看下面代码:

Char flower[10]=rose;

Cout<<flower<<s are red/n;

数组名是第一个元素的地址,因此cout语句中的flower是包含字符r的char元素的地址。Cout对象认为char地址是字符串的地址,因此打印该地址出的字符,然后继续打印后面的字符,知道遇到空字符为止。

8.2使用new创建动态结构

new用于结构由两步组成:创建结构和访问其成员。要创建结构,需要同时使用结构类型和new。例如,要创建一个未命名的inflatable类型,并将其地址赋给一个指针,可以这样做:Inflatable* ps=new inflatable;

接下来要访问成员。创建动态结构不能将成员运算符句点用于结构名,因为这结构没有名称,只是知道它的地址。C++专门为这种情况提供了一个运算符:箭头成员运算符(->),即ps->p。

另一种访问结构成员的方法,如果ps是指向结构的指针,则*ps就是被指向的值——结构本身。由于*ps是一个结构,因此(*ps).price是该结构的price成员。

8.3自动存储、静态存储和动态存储

(1)自动存储

在函数内部定义的常规变量使用自动存储空间,被称为自动变量,这意味着它们所属函数被调用时自动产生,在函数结束时消亡。如果getname()函数返回temp的地址,则main()中的name指针指向的内存将很快得到重新使用。这就是在getname()中使用new的原因之一。

实际上,自动变量是一个局部变量。自动变量通常存储在栈中。这意味着执行代码时,其中的变量将依次加入到栈中,而在离开代码块时,将按相反的顺序释放这些变量,这被称为后进先出(LIFO)。因此,在程序执行中,栈将不断地增加和缩小。

(2)静态存储

静态存储是整个程序执行期间都存在地存储方式。使变量称为静态的方式有两种:一种使在函数外面定义它;另一种是在声明变量时使用关键字static。

Static double fee=56.50;

(3)动态存储

New和delete运算符提供了一种比自动变量和静态变量更灵活的方法。它们管理一个内存池,这在C++中被称为自由存储空间或堆。该内存池同用于静态变量和自动变量的内存是分开的。New和delete让您能够在一个函数中分配内存,而在另一个函数中释放它。因此,数据的生命周期不完全受到程序或函数的生存时间控制。与使用常规变量相比,使用new和delete让程序员对程序如何使用内存有更大的控制权。然而内存管理更为复杂了。在栈中,自动添加和删除机制使得占用的内存总是连续的,但new和delete的相互影响可能导致占用的自由存储区不连续,这使得跟踪新分配内存的位置更加困难。

8.4栈、堆和内存泄漏

   如果使用new运算符在自由存储空间(或堆)上创建变量后,没有调用delete,则即使包含指针的内存由于作用域规则和对象生命周期的原因而被释放,在自由存储空间上动态分配的变量或结构也将继续存在。实际上,将会无法访问自由存储空间中的结构,因为指向这些内存的指无效。这将会导致内存泄漏。被泄漏的内存将在程序的整个生命周期内都不可使用;这些内存被分配出去,但无法收回。

九、数组的替代品

模板类vector和array是数组的替代品。

1、模板类vector

模板类vector类似于string类,也是一种动态数组。可以在运行阶段设vector对象的长度,也可以在末尾添加数据,还可在中间插入数据。基本上,它是使用new创建动态数组的替代品。实际上,vector类确实使用new和delete来管理内存,但这种工作是自动完成的。

要使用vector对象,必须要包含头文件vector。其次vector包含在名称空间std种,因此需要使用using编译指令;第三,模板使用不同的语法来指出它存储的数据类型;第四,vector类使用不同的语法来指定元素数。其中n_elem可以是变量。

如:vector<typename> vt(n_elem);

Vt是一个vector<typename> 对象。

由于vector对象在您插入或添加值时自动调整长度,因此可以将vt的初始长度设置为零。但要调整长度,需要使用vector包种的各种方法。

2、模板类array

Vector类的功能比数组强大,但付出的代价是效率稍低。如果您需要的是长度固定的数组,使用数组是更佳的选择,但代价是不那么方便和安全。因此,使用array模板类,array对象的长度也是固定的,也使用栈,而不是自由存储区,因此其效率与数组相同,但更方遍和安全。

 创建array对象,需要包含头文件array。Array对象的创建语法与vector稍有不同。

vector<typename,n_elem> arr;

其中n_elem不能是变量。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值