C++ Primer Plus(第六版)--学习杂记(第四章)
2015-09-18 20:50
507 查看
1.
如果将sizeof运算符用于数组名,得到的将是整个数组中的字节数。但如果将sizeof用于数组元素,则得到的将是元素的程度(单位是字节)。表明yams是一个数组,而yams[1]只是一个int变量
2.
只有定义数组时才能够初始化,以后就不能使用了,也不能将一个数组赋给另外一个数组
3.
让编译器去数出元素个数
4.
这个程序很简单:读取来自键盘的用户名和用户喜欢的甜点,然后显示这些信息,输出结果如下:
程序中的这个缺陷被精心选择输入被掩盖了。
还没对用户感性的甜点进行泛型,程序就显示出来了。
由于cin不能使用空字符,因此cin需要用别的方法来确定字符串结尾的位置,cin使用空白(空格、制表符、换行符)来确定字符串的结束位置。这意味着cin在字符数组输入时只能读取一个单词,读取这个单词后,cin将该字符串放到数组中,并自动在结尾添加空字符。
5.
istream中的类(如cin)提供了一些面向行的类成员函数:getline()和get(),这两个函数都能读取一行收入,直到换行符。然后getline将丢弃换行符,而get()将换行符保留在输序列中
6.
getline有两个参数,第一个是数组的名称,第二个是读取的字符数(最多读取的字符是参数-1,留位置给\0)
这就可以读取完整的姓名和甜点信息了
第一次调用后,换行符将留在输入队列中,因此第二次调用时看到的第一个字符表示换行符,此get()认为已经到达行尾了,而没有发现任何可读取的内容。如果不借助帮助,get()将不能跨过这个换行符。
老式实现中没有getline(),其次,get()使输入更仔细。例如,假如用get()将一行读入数组中。如何知道停止读取的原因是由于已经读取了整行,而不是由于数组已填满呢?查看下一个输入字符,如果是换行符,说明已经读取了整行,否则,说明还有其他输入。getline使用更容易些,但get()是的检查错误更简单些。
7.
当cin读取number的时候,将回车键生成的换行符留在输入队列中,后面的cin.getline()看到换行符后,将认为是个空行,并将空字符赋给address,解决方法是读取丢弃了的换行符
即可
8.
C++更多使用指针而不是数组来处理字符串
9.
类设计让程序能够自动处理string的大小,更方便与安全
10.
字符串可以相加, \” 在这里用作的是双引号的作用
11.
使用函数strcpy( )将字符串复制到字符数组,使用strcat( )将字符串附加到字符数组末尾:
str1是一个对象,而size( )是一个类方法,方法是一个函数,只能通过其所属类的对象进行调用,在这里,str1是一个string对象,而size( )是string类的一个方法。C++string类对象使用对象名和句点运算符来指出要使用哪个字符串
12.
在用户输入之前,该程序指出数组charr中的字符长度是27,这比该数组的长度要大。首先,未初始化的内容是未定义的;其次,函数strlen()从数组的第一个元素开始计算字节数,直到遇到空字符。在这个例子中,在数组末尾的几个字节后才遇到空字符。对于未被初始化的数据,第一个空字符的出现位置是随机的,因此您在运行该程序时,得到的数组长度也很可能与此不同(我的是3)
未被初始化的string对象的长度都自动设置为0
13.
cin是istream对象
14.
访问类成员函数的方式是从访问结构成员变量的方式衍生出来的
15.
注意结构体的外部声明和内部声明
16.
C++使用户定义的类型与内置类型尽可能相似,例如,可以将结构作为参数传递给函数,也可以让函数返回一个结构。另外,还可以使用赋值运算符将结构赋值给另外一个同类型的结构,这样的结构中每个成员都将被设置为另外一个结构中相应成员的值,即使成员是数组,这种赋值被称为成员赋值
成员赋值是有效的
17.
结构数组
inflatable guest[2]=
{
{“dad”,123,5.23},
{“mom”,789,0.98}
};
18.
位字段常用于低级编程
19.
共用体是一种数据格式,它能存储不同的数据类型,但只能同时存储其中一种类型,其他的话都是在不同时间下进行的。共用体的用途之一是,当数据项使用两种或更多格式(但不会同时使用)时,可节省空间。只要勇于嵌入式系统、系统的数据结构或硬件数据结构。
20.
在C++中创建指针,计算机将分配用来存储地址的内存,而不会分配用来存储指针所指向数据的内存。
21.
不能简单给把整数赋值给指针:
int *pt;
pt=0xB8000000; //type not match
pt=(int *)0xB8000000; //type now match
22.
指针初始化为变量的地址,变量是在编译时分配的有名称的内存,而指针只是为可以通过名称直接访问的内存提供别名。指针真正的用武之地在于,在运行阶段分配未命名的内存以存储值。在这种情况下,只能通过指针来访问内存,在C语言中,使用malloc( )来分配内存,而C++中还有更好的办法 ~ new运算符
在运行阶段为一个int值分配未命名的内存,并使用指针来访问这个值。程序员要告诉new,需要为哪种数据类型分配内存;new将找到一个长度正确的内存块,并返回该内存块的地址。程序员的责任是将该地址赋给一个指针。
int * pn = new int;
new int告诉程序,需要适合int的内存。new运算符根据类型来确定需要多少字节的内存,然后它找到这样的内存,并返回其地址,接下来将地址赋给pn,pn被声明为指向int的指针。而现在,pn是地址,*pn是存储在那里的值。将这种方法与将变量的地址赋给指针进行比较:
int a;
int * pt=&a;
这两种情况都是将一个int变量的地址赋给指针。第二种情况下,可以通过名称a来访问该int,在第一种情况下,则只能通过该指针进行访问。这引出了一个问题:pn指向的内存没有名字,如何称呼它?我们说pn指向一个数据对象,指的是数据项分配的内存块,因此变量也是数据对象,但pn指向的内存不是变量。处理数据对象的指针方法在管理内存方面有更大的控制权。
23.
new分配的内存块与常规变量声明分配的内存块不同,变量都存储在被称为栈的内存区域,而new从被称为堆或自由存储区的区域分配内存
24.
计算机可能会因为没有足够的内存而不能满足new的请求,在这种情况下,new通常会引发异常,一种错误处理技术,而在较老的实现中,new返回的是0,在C++中,值为0的指针被称为空指针。C++确保空指针不会指向有效的数据,因此它常被用来表示运算符或函数失败(函数成果会返回有用的指针)。C++提供了检测并处理内存分配失败的工具。
25.
当需要内存时通过new来请求,使用delete,它使在使用完内存后,能够将其归还给内存池,这有利于有效使用内存。归还或释放的内存可以供程序的其他部分使用。使用delete时,后面要加上指向内存块的指针。
int *ps = new int;
…
delete ps;
这将释放ps所指向的内存,但不会删除指针ps本身,例如,可以将ps重新指向另一个新分配的内存块,一定要配对使用new和delete,否则将发生内存泄露,也就是说,被分配的内存再也无法使用了,如果内存泄露严重的话,则程序将由于不断寻找更多的内存而终止。
不要尝试释放已经释放的内存块
int *ps = new int;
…
delete ps;
delete ps;//not allowed
对于空指针使用delete是安全的
26.
int *ps = new int;
int *pq=ps;
…
delete ps; //两个指针都删了
一般来说,不要创建两个同时指向同一个内存块的指针,因为这会增加错误地删除同一内存块两次的可能性
27.
如果通过声明来创建数组,则在程序被编译时将为它分配内存空间。不管程序最终是否使用数组,数组都在那,占用着内存。在编译时给数组分配内存叫做静态联翩,意味着数组是在编译时加入到程序中的,但是用new时,如果在运行阶段使用数组,则创建它;如果不需要,则不创建。还可以在程序运行时选择数组的长度,这叫做动态联编,意味着数组是在程序运行时创建的。这叫动态数组,静态联翩时,必须在编写程序时指定数组的长度;使用动态联编时,程序将在运行时确定数组的长度。
28.
用new创建动态数组:
int *psome = new int [10];
new运算符返回第一个元素的地址,该地址赋给了psome
当程序使用完new分配的内存块时,应使用delete释放它们。然而,对于使用new创建的数组,应使用另外一种格式delete释放
delete [ ] psome;
方括号告诉程序,应释放整个数组,而不仅仅是指针指向的元素。
使用new和delete应注意:
*不要使用delete来释放不是new分配的内存
*不要使用delete释放同一个内存两次
*如果使用new[ ]为数组分配内存,则应使用delete [ ]来释放
*如果使用new为一个实体分配内存,则应用delete(没有方括号)来释放
*对空指针应用delete是安全的
不能使用sizeof运算符来确定动态分配数组所包含的字节数
psome指向10个int的内存块中的第一个元素,假设int占4个字节,则psome沿正确的方向移动4个字节指向到第2个元素,总共10个元素,这就是psome移动的范围,因此new语句提供识别内存块中每个元素所需要的全部信息
数组名和指针之间的根本区别,数组名是不能修改的,但指针是变量,因此可以修改其的值,指针最后指向原来的值,这样程序给delete[ ]提供了正确的地址。相邻的int地址通常相差2个字节或4个字节,而p3加上1后直接指向下一个元素的地址,这就是指针算术特别的地方。
29.
指针和数组基本等价的原因在于指针算术和C++内部处理数组的方式。整数变量+1后,其值将增加1,但将还真变量加1后,增加的量等于它指向类型的字节数。
sizeof(wages)得到的是数组的长度
sizeof(pw)得到的是指针的长度
30.
数组名并不完全解释为数组的地址
数组名被解释为其第一个元素的地址,而对数组名应用地址运算符时,得到的是整个数组的地址
short tell[10];
cout<
35.
const char * bird = “wren”;
“wren”实际表示的是字符串的地址,这条语句将“wren”的地址赋给bird指针(一般来说,编译器在内存中留出一些空间,以存储程序源代码中所有用引号括起来的字符串,并将每个被存储的字符串与其地址关联起来),这意味可以像使用字符串“wren”那样使用指针bird
cout<<”A concerned “<
36.
getname( )返回一个指向输入字符串的内存块,并返回一个指向该内存块的指针,对于大量读取字符串的程序,这种方法能节省大量的内存。
假设程序需要读取1000个字符串,其中最大的字符串包含79个字符,而大多数字符串都短得多。如果用char数组来存储这些字符串,则需要1000个数组,其中每个数组的长度为80个字符。总共需要80000个字符,而其中很多内存没有被使用。另外一种办法是,创建一个数组,它包含1000个指向char的指针,然后使用new根据每个字符串的需要分配相应数量的内存。这将节省几万个字节。
函数getname( ),它使用cin将输入的单词放到temp数组中,然后使用new重新分配新内存,以存储该单词。程序需要strlen(temp)+1个字符(包括空字符)来存储该字符串,因此将这个值给new。获得空间后getname( )使用strcnpy( )将temp中的字符串复制到新的内存块中,该函数不检查内存块是否能容纳字符串,但getname( )通过使用new请求适合的字节数来完成了这样的工作。最后,函数返回pn,这就是字符串副本的地址。
返回值赋给指针name,然后指向getname( )函数中分配的内存块,最后程序打印该字符和地址。在释放name指向的内存块后,main( )再次调用getname( )。C++不保证新释放的内存就是下一次使用new时选择的内存,运算结果求证了。
在这里例子中,getname( )分配内存,而main( )释放内存,将new和delete放在不同函数中通常不是一个好办法,因为这样很容易忘记使用delete。
37.
根据用于分配内存的方法,C++有三种管理内存的方式:自动存储、静态存储和动态存储(或称自由存储或堆)
38.
new和delete运算符提供了一种比自动变量和静态变量更灵活的办法,它们管理一个内存池,这在C++中被称为自由存储空间或堆,该内存池同用于静态变量和自动变量的内存时分开的。new和delete让你能够在一个函数中分配内存,而在另外一个函数中释放它。数据的声明不完全受程序或函数的生存时间控制。与使用常规变量相比,使用new和delete让程序员对如何使用内存有更大的控制权。然而,内存管理也更加复杂。在栈中,自动添加和删除是的占用的内存总是连续的,但new和delete的相互影响可能导致占用的自由存储区不连续,这使得跟踪分配内存的位置更困难。
39.
使用new运算符在自由存储空间或堆上创建变量后,没有调用delete,则即使包含指针的内存由于作用域规则和对象生命周期的原因而被释放,在自由存储空间上动态分配的变量或结构也将也将继续存在。实际上,将会无法自由访问自由存储空间中的结构,因为指向这些内存的指针无效。这将导致内存泄露。被泄露的内存将在程序的整个生命周期内都不可使用;这些内存被分配出去,但无法回收。极端情况下,内存泄露可能会非常严重,以致于应用程序可用的内存被耗尽,出现内存耗尽错误,导致程序崩溃。另外,这种泄露还会给一些操作系统或在相同的内存空间中运行的应用程序带来负面影响,导致它们崩溃。
40.
41.
模板类vector类似于string类,也是一种动态数组。可以在运行阶段设置vector对象的长度,可在末尾附加新数据,还可在中间插入数据,基本上,它是使用new创建动态数组动态数组的替代品,new和delete自动管理内存
42.
vector vt(n_elem)
n_elem可以是整型常量或整型变量
vector类的功能强大,但付出的代价是效率稍低。
43.
如果使用长度固定的数组,不那么安全和方便,C++11中增加了模板array,它也在命名空间std中,array的长度固定,使用栈(静态内存分配),效率与数组相同,但更方便和安全。
array
数组,vector对象和array对象,都可使用标准数组表示法来访问各个元素。vector存储在堆,而数组和array存储在栈。array对象可以赋值给另外一个array对象,而对于数组,必须逐个复制数据
a1[-2]=20.2;
含义是*(a1-2) = 20.2;找到a1指向的地方,向前移动两个double元素,并将20.2存储到目的地,也就是说将信息存储到数组外面,与C语言一样,C++不检查这种超界错误。在这个实例中,这个位置至于array对象a3中。其他编译器可能放在a4中,甚至做出更糟糕的选择,这表明数组的行为是不安全的
44.
用enum定义一个名为Response的类型,它包含Yes=1,No=0,Maybe=2
enum Response {No,Yes,Maybe};
45.
编写一段代码,要求用户输入一个正整数,然后创建一个动态int数组,其中包含的元素数目等于用户输入的值。首先使用new来完成这个任务,再使用vector对象来完成这个任务。
46.
cout<<(int *) “Home of the jolly bytes”; 的含义是?
表达式”Home of the jolly bytes”是一个字符串常量,因此,它将判断为字符串开始的地址。cout对象将char地址解释为打印字符串,但类型(int *)将地址转换为int指针,然后作为地址被打印
47.
声明一个vector对象和一个array对象,它们都包含10个string对象,指出所需的头文件但不使用using,使用const来制定要包含的string对象
48.
49.
字符串和getline( )的结合
50.
char数组实现名字拼接
string数组实现名字拼接
51.
52.
53.
如果将sizeof运算符用于数组名,得到的将是整个数组中的字节数。但如果将sizeof用于数组元素,则得到的将是元素的程度(单位是字节)。表明yams是一个数组,而yams[1]只是一个int变量
2.
只有定义数组时才能够初始化,以后就不能使用了,也不能将一个数组赋给另外一个数组
3.
让编译器去数出元素个数
4.
#include<iostream> using namespace std; int main() { const int ArSize = 20; char name[ArSize]; char dessert[ArSize]; cout<<"Enter your name: "<<endl; cin>>name; cout<<"Enter your favourite dessert: "<<endl; cin>>dessert; cout<<"I have some delicious "<<dessert; cout<<" for you, "<<name<<".\n"; return 0; }
这个程序很简单:读取来自键盘的用户名和用户喜欢的甜点,然后显示这些信息,输出结果如下:
Enter your name: Huang Hai Enter your favourite dessert: I have some delicious Hai for you, Huang
程序中的这个缺陷被精心选择输入被掩盖了。
还没对用户感性的甜点进行泛型,程序就显示出来了。
由于cin不能使用空字符,因此cin需要用别的方法来确定字符串结尾的位置,cin使用空白(空格、制表符、换行符)来确定字符串的结束位置。这意味着cin在字符数组输入时只能读取一个单词,读取这个单词后,cin将该字符串放到数组中,并自动在结尾添加空字符。
5.
istream中的类(如cin)提供了一些面向行的类成员函数:getline()和get(),这两个函数都能读取一行收入,直到换行符。然后getline将丢弃换行符,而get()将换行符保留在输序列中
6.
getline有两个参数,第一个是数组的名称,第二个是读取的字符数(最多读取的字符是参数-1,留位置给\0)
#include<iostream> using namespace std; int main() { const int ArSize = 20; char name[ArSize]; char dessert[ArSize]; cout<<"Enter your name: "<<endl; cin.getline(name,ArSize); cout<<"Enter your favourite dessert: "<<endl; cin.getline(dessert,ArSize); cout<<"I have some delicious "<<dessert; cout<<" for you, "<<name<<".\n"; return 0; }
这就可以读取完整的姓名和甜点信息了
cin.get(name,ArSize); cin.get(dessert,ArSize);//a problem
第一次调用后,换行符将留在输入队列中,因此第二次调用时看到的第一个字符表示换行符,此get()认为已经到达行尾了,而没有发现任何可读取的内容。如果不借助帮助,get()将不能跨过这个换行符。
(1) cin.get(name,ArSize); cin.get(); cin.get(dessert,ArSize); (2) cin.get(name,ArSize).get();//将两个类成员函数拼接起来 cin.get(dessert,ArSize).get();
老式实现中没有getline(),其次,get()使输入更仔细。例如,假如用get()将一行读入数组中。如何知道停止读取的原因是由于已经读取了整行,而不是由于数组已填满呢?查看下一个输入字符,如果是换行符,说明已经读取了整行,否则,说明还有其他输入。getline使用更容易些,但get()是的检查错误更简单些。
7.
#include<iostream> using namespace std; int main() { const int ArSize = 20; char number; char dessert[ArSize]; cout<<"Enter your number: "<<endl; cin>>number; cout<<"Enter your favourite dessert: "<<endl; cin.getline(dessert,ArSize); cout<<"I have some delicious "<<dessert; cout<<" for you, "<<number<<".\n"; return 0; }
当cin读取number的时候,将回车键生成的换行符留在输入队列中,后面的cin.getline()看到换行符后,将认为是个空行,并将空字符赋给address,解决方法是读取丢弃了的换行符
(1) cin>>year; cin.get(); (2) (cin>>year).get();
即可
8.
C++更多使用指针而不是数组来处理字符串
9.
类设计让程序能够自动处理string的大小,更方便与安全
10.
#include<iostream> #include<string> using namespace std; int main() { string s1="penguin"; string s2,s3; cout<< "You can assign one string object to another: s2 = s1 \n"; s2=s1; cout<<"s1: "<<s1<<" , s2: "<<s2<<endl; cout<<"You can assign a C-style string to a string object.\n"; cout<<"s2 = \"buzzard\""<<endl; s2="buzzard"; cout<<"s2: "<<s2<<endl; cout<<"You can concatenate strins: s3=s1+s2"<<endl; s3=s1+s2; cout<<"s3: "<<s3<<endl; cout<<"You can qppend strings."<<endl; s1+=s2; cout<<"s1 +=s2 yields s1 = "<<s1<<endl; s2 +=" for a day"; cout<<" s2 += \" for a day\" yields s2 = "<<s2<<endl; return 0; }
字符串可以相加, \” 在这里用作的是双引号的作用
11.
使用函数strcpy( )将字符串复制到字符数组,使用strcat( )将字符串附加到字符数组末尾:
#include<iostream> #include<string> #include<cstring> using namespace std; int main() { char charr1[20]; char charr2[20] = "jaguar"; string str1; string str2 = "panther"; str1=str2; strcpy(charr1,charr2); str1+=" paste"; strcat(charr1," juice"); int len1 = str1.size(); int len2 = strlen(charr1); cout<<"The string "<<str1<<" contains " <<len1<<" characters."<<endl; cout<<"The string "<<charr1<<" contains " <<len2<<" characters."<<endl; }
str1是一个对象,而size( )是一个类方法,方法是一个函数,只能通过其所属类的对象进行调用,在这里,str1是一个string对象,而size( )是string类的一个方法。C++string类对象使用对象名和句点运算符来指出要使用哪个字符串
12.
#include<iostream> #include<string> #include<cstring> using namespace std; int main() { char charr[20]; string str; cout<<"Length of string in charr before input: " <<strlen(charr)<<endl; cout<<"Length of string in str before input: " <<str.size()<<endl; cout<<"Enter a line of text:"<<endl; cin.getline(charr,20); cout<<"You entered: "<<charr<<endl; cout<<"Enter anthor line of text: "<<endl; getline(cin,str); cout<<"You entered: "<<str<<endl; cout<<"Length of string in charr after input: " <<strlen(charr)<<endl; cout<<"Length of string in charr after input: " <<str.size()<<endl; return 0; }
在用户输入之前,该程序指出数组charr中的字符长度是27,这比该数组的长度要大。首先,未初始化的内容是未定义的;其次,函数strlen()从数组的第一个元素开始计算字节数,直到遇到空字符。在这个例子中,在数组末尾的几个字节后才遇到空字符。对于未被初始化的数据,第一个空字符的出现位置是随机的,因此您在运行该程序时,得到的数组长度也很可能与此不同(我的是3)
未被初始化的string对象的长度都自动设置为0
13.
cin是istream对象
14.
访问类成员函数的方式是从访问结构成员变量的方式衍生出来的
15.
注意结构体的外部声明和内部声明
16.
C++使用户定义的类型与内置类型尽可能相似,例如,可以将结构作为参数传递给函数,也可以让函数返回一个结构。另外,还可以使用赋值运算符将结构赋值给另外一个同类型的结构,这样的结构中每个成员都将被设置为另外一个结构中相应成员的值,即使成员是数组,这种赋值被称为成员赋值
#include<iostream> using namespace std; struct inflatable { char name[20]; float volumn; double price; }; int main() { inflatable bouquet = { "sunflowers", 0.20, 12.49 }; inflatable choice; cout<<"bouquet: "<<bouquet.name<< " for $"; cout<<bouquet.price<<endl; choice=bouquet; cout<<"choice: "<<choice.name<< " for $"; cout<<choice.price<<endl; }
成员赋值是有效的
//同时完成定义结构和创建结构的工作 (1) struct perks { ...... }a,b; (2) struct perks { }a = { ..... }; (3) struct //声明没有名称的结构类型 { ..... }a; 这以后无法创建这种类型的变量
17.
结构数组
inflatable guest[2]=
{
{“dad”,123,5.23},
{“mom”,789,0.98}
};
18.
位字段常用于低级编程
19.
共用体是一种数据格式,它能存储不同的数据类型,但只能同时存储其中一种类型,其他的话都是在不同时间下进行的。共用体的用途之一是,当数据项使用两种或更多格式(但不会同时使用)时,可节省空间。只要勇于嵌入式系统、系统的数据结构或硬件数据结构。
20.
在C++中创建指针,计算机将分配用来存储地址的内存,而不会分配用来存储指针所指向数据的内存。
21.
不能简单给把整数赋值给指针:
int *pt;
pt=0xB8000000; //type not match
pt=(int *)0xB8000000; //type now match
22.
指针初始化为变量的地址,变量是在编译时分配的有名称的内存,而指针只是为可以通过名称直接访问的内存提供别名。指针真正的用武之地在于,在运行阶段分配未命名的内存以存储值。在这种情况下,只能通过指针来访问内存,在C语言中,使用malloc( )来分配内存,而C++中还有更好的办法 ~ new运算符
在运行阶段为一个int值分配未命名的内存,并使用指针来访问这个值。程序员要告诉new,需要为哪种数据类型分配内存;new将找到一个长度正确的内存块,并返回该内存块的地址。程序员的责任是将该地址赋给一个指针。
int * pn = new int;
new int告诉程序,需要适合int的内存。new运算符根据类型来确定需要多少字节的内存,然后它找到这样的内存,并返回其地址,接下来将地址赋给pn,pn被声明为指向int的指针。而现在,pn是地址,*pn是存储在那里的值。将这种方法与将变量的地址赋给指针进行比较:
int a;
int * pt=&a;
这两种情况都是将一个int变量的地址赋给指针。第二种情况下,可以通过名称a来访问该int,在第一种情况下,则只能通过该指针进行访问。这引出了一个问题:pn指向的内存没有名字,如何称呼它?我们说pn指向一个数据对象,指的是数据项分配的内存块,因此变量也是数据对象,但pn指向的内存不是变量。处理数据对象的指针方法在管理内存方面有更大的控制权。
23.
new分配的内存块与常规变量声明分配的内存块不同,变量都存储在被称为栈的内存区域,而new从被称为堆或自由存储区的区域分配内存
24.
计算机可能会因为没有足够的内存而不能满足new的请求,在这种情况下,new通常会引发异常,一种错误处理技术,而在较老的实现中,new返回的是0,在C++中,值为0的指针被称为空指针。C++确保空指针不会指向有效的数据,因此它常被用来表示运算符或函数失败(函数成果会返回有用的指针)。C++提供了检测并处理内存分配失败的工具。
25.
当需要内存时通过new来请求,使用delete,它使在使用完内存后,能够将其归还给内存池,这有利于有效使用内存。归还或释放的内存可以供程序的其他部分使用。使用delete时,后面要加上指向内存块的指针。
int *ps = new int;
…
delete ps;
这将释放ps所指向的内存,但不会删除指针ps本身,例如,可以将ps重新指向另一个新分配的内存块,一定要配对使用new和delete,否则将发生内存泄露,也就是说,被分配的内存再也无法使用了,如果内存泄露严重的话,则程序将由于不断寻找更多的内存而终止。
不要尝试释放已经释放的内存块
int *ps = new int;
…
delete ps;
delete ps;//not allowed
对于空指针使用delete是安全的
26.
int *ps = new int;
int *pq=ps;
…
delete ps; //两个指针都删了
一般来说,不要创建两个同时指向同一个内存块的指针,因为这会增加错误地删除同一内存块两次的可能性
27.
如果通过声明来创建数组,则在程序被编译时将为它分配内存空间。不管程序最终是否使用数组,数组都在那,占用着内存。在编译时给数组分配内存叫做静态联翩,意味着数组是在编译时加入到程序中的,但是用new时,如果在运行阶段使用数组,则创建它;如果不需要,则不创建。还可以在程序运行时选择数组的长度,这叫做动态联编,意味着数组是在程序运行时创建的。这叫动态数组,静态联翩时,必须在编写程序时指定数组的长度;使用动态联编时,程序将在运行时确定数组的长度。
28.
用new创建动态数组:
int *psome = new int [10];
new运算符返回第一个元素的地址,该地址赋给了psome
当程序使用完new分配的内存块时,应使用delete释放它们。然而,对于使用new创建的数组,应使用另外一种格式delete释放
delete [ ] psome;
方括号告诉程序,应释放整个数组,而不仅仅是指针指向的元素。
使用new和delete应注意:
*不要使用delete来释放不是new分配的内存
*不要使用delete释放同一个内存两次
*如果使用new[ ]为数组分配内存,则应使用delete [ ]来释放
*如果使用new为一个实体分配内存,则应用delete(没有方括号)来释放
*对空指针应用delete是安全的
不能使用sizeof运算符来确定动态分配数组所包含的字节数
psome指向10个int的内存块中的第一个元素,假设int占4个字节,则psome沿正确的方向移动4个字节指向到第2个元素,总共10个元素,这就是psome移动的范围,因此new语句提供识别内存块中每个元素所需要的全部信息
#include<iostream> using namespace std; int main() { double * p3 = new double [3]; p3[0]=0.2; p3[1]=0.5; p3[2]=0.8; cout<<"p3[1] is "<<p3[1]<<endl; p3++; cout<<"Now p3[0] is "<<p3[0]<<" and "; cout<<"p3[1] is "<<p3[1]<<endl; p3--; delete [] p3; return 0; }
数组名和指针之间的根本区别,数组名是不能修改的,但指针是变量,因此可以修改其的值,指针最后指向原来的值,这样程序给delete[ ]提供了正确的地址。相邻的int地址通常相差2个字节或4个字节,而p3加上1后直接指向下一个元素的地址,这就是指针算术特别的地方。
29.
指针和数组基本等价的原因在于指针算术和C++内部处理数组的方式。整数变量+1后,其值将增加1,但将还真变量加1后,增加的量等于它指向类型的字节数。
#include<iostream> using namespace std; int main() { double wages[3]={1000.0,2000.0,3000.0}; short stacks[3]={3,2,1}; double * pw = wages; short * ps = &stacks[0]; cout<<"pw = "<<pw<<", *pw= "<<*pw<<endl; pw++; cout<<"add 1 to the pw pointer"<<endl; cout<<"pw = "<<pw<<", *pw= "<<*pw<<endl<<endl; cout<<"ps = "<<ps<<", *ps= "<<*ps<<endl; ps++; cout<<"add 1 to the ps pointer"<<endl; cout<<"ps = "<<ps<<", *ps= "<<*ps<<endl; cout<<"access two element with array notation "<<endl; cout<<"stacks[0] = "<<stacks[0]<<", stacks[1] = "<<stacks[1]<<endl; cout<<"access two element with pointer notation "<<endl; cout<<"*stacks = "<<*stacks<<", *(stacks+1) = "<<*(stacks+1)<<endl; cout<<sizeof(wages)<<" = size of wages array"<<endl; cout<<sizeof(pw)<<" = size of pw pointer"<<endl; return 0; }
sizeof(wages)得到的是数组的长度
sizeof(pw)得到的是指针的长度
30.
数组名并不完全解释为数组的地址
数组名被解释为其第一个元素的地址,而对数组名应用地址运算符时,得到的是整个数组的地址
short tell[10];
cout<
int size; cin>>size; int *pz = new int [size]; ... delete [] pz;
35.
const char * bird = “wren”;
“wren”实际表示的是字符串的地址,这条语句将“wren”的地址赋给bird指针(一般来说,编译器在内存中留出一些空间,以存储程序源代码中所有用引号括起来的字符串,并将每个被存储的字符串与其地址关联起来),这意味可以像使用字符串“wren”那样使用指针bird
cout<<”A concerned “<
//使用new创建一个未命名的结构,并演示了两种访问结构的指针表示法 #include<iostream> using namespace std; struct inflatable { char name[20]; float volumn; double price; }; int main() { inflatable *ps= new inflatable; cout<<"Enter name of inflatable item: "; cin.get(ps->name,20); cout<<"Enter volumn in cubic feet: "; cin>>(*ps).volumn; cout<<"Enter price: $"; cin>>ps->price; cout<<"Name: "<<(*ps).name<<endl; cout<<"Volumn: "<< ps->volumn<<endl; cout<<"Proce: $"<<(*ps).price<<endl; delete ps; return 0; }
36.
#include<iostream> #include<cstring> using namespace std; char * getname(void); int main() { char * name; name = getname(); cout<<name<<" at "<<(int *)name<<endl; delete [] name; name = getname(); cout<<name<<" at "<<(int *)name<<endl; delete [] name; return 0; } char * getname() { char temp[80]; cout<<"Enter last name: "; cin>>temp; char * pn = new char[strlen(temp)+1]; strcpy(pn,temp); return pn; }
getname( )返回一个指向输入字符串的内存块,并返回一个指向该内存块的指针,对于大量读取字符串的程序,这种方法能节省大量的内存。
假设程序需要读取1000个字符串,其中最大的字符串包含79个字符,而大多数字符串都短得多。如果用char数组来存储这些字符串,则需要1000个数组,其中每个数组的长度为80个字符。总共需要80000个字符,而其中很多内存没有被使用。另外一种办法是,创建一个数组,它包含1000个指向char的指针,然后使用new根据每个字符串的需要分配相应数量的内存。这将节省几万个字节。
函数getname( ),它使用cin将输入的单词放到temp数组中,然后使用new重新分配新内存,以存储该单词。程序需要strlen(temp)+1个字符(包括空字符)来存储该字符串,因此将这个值给new。获得空间后getname( )使用strcnpy( )将temp中的字符串复制到新的内存块中,该函数不检查内存块是否能容纳字符串,但getname( )通过使用new请求适合的字节数来完成了这样的工作。最后,函数返回pn,这就是字符串副本的地址。
返回值赋给指针name,然后指向getname( )函数中分配的内存块,最后程序打印该字符和地址。在释放name指向的内存块后,main( )再次调用getname( )。C++不保证新释放的内存就是下一次使用new时选择的内存,运算结果求证了。
在这里例子中,getname( )分配内存,而main( )释放内存,将new和delete放在不同函数中通常不是一个好办法,因为这样很容易忘记使用delete。
37.
根据用于分配内存的方法,C++有三种管理内存的方式:自动存储、静态存储和动态存储(或称自由存储或堆)
38.
new和delete运算符提供了一种比自动变量和静态变量更灵活的办法,它们管理一个内存池,这在C++中被称为自由存储空间或堆,该内存池同用于静态变量和自动变量的内存时分开的。new和delete让你能够在一个函数中分配内存,而在另外一个函数中释放它。数据的声明不完全受程序或函数的生存时间控制。与使用常规变量相比,使用new和delete让程序员对如何使用内存有更大的控制权。然而,内存管理也更加复杂。在栈中,自动添加和删除是的占用的内存总是连续的,但new和delete的相互影响可能导致占用的自由存储区不连续,这使得跟踪分配内存的位置更困难。
39.
使用new运算符在自由存储空间或堆上创建变量后,没有调用delete,则即使包含指针的内存由于作用域规则和对象生命周期的原因而被释放,在自由存储空间上动态分配的变量或结构也将也将继续存在。实际上,将会无法自由访问自由存储空间中的结构,因为指向这些内存的指针无效。这将导致内存泄露。被泄露的内存将在程序的整个生命周期内都不可使用;这些内存被分配出去,但无法回收。极端情况下,内存泄露可能会非常严重,以致于应用程序可用的内存被耗尽,出现内存耗尽错误,导致程序崩溃。另外,这种泄露还会给一些操作系统或在相同的内存空间中运行的应用程序带来负面影响,导致它们崩溃。
40.
#include<iostream> using namespace std; struct begin { int year; }; int main() { begin s01,s02,s03; s01.year = 1998; begin * pa = &s02; pa->year = 1999; begin trio[3]; trio[0].year = 2003; (trio+1)->year = 2004; cout<<trio->year<<endl; cout<<(trio+1)->year<<endl; const begin * arp[3] = {&s01,&s02,&s03}; cout<<arp[1]->year<<endl; const begin ** ppa = arp; //auto ppb = arp; 适用于C++11 cout<<(*ppa)->year<<endl; return 0; }
41.
模板类vector类似于string类,也是一种动态数组。可以在运行阶段设置vector对象的长度,可在末尾附加新数据,还可在中间插入数据,基本上,它是使用new创建动态数组动态数组的替代品,new和delete自动管理内存
42.
#include<vector> ... using namespace std; vector<int> vi; int n; cin>>n; vector<double> vd(n);
vector vt(n_elem)
n_elem可以是整型常量或整型变量
vector类的功能强大,但付出的代价是效率稍低。
43.
如果使用长度固定的数组,不那么安全和方便,C++11中增加了模板array,它也在命名空间std中,array的长度固定,使用栈(静态内存分配),效率与数组相同,但更方便和安全。
#include<array> ...... using namespace std; array<int,5> ai; array<double,4> ad = {1.,3.1,4.5,4.4};
array
#include<iostream> #include<vector> //#include<array> using namespace std; int main() { double a1[4] = {1.2,2.4,3.6,4.8}; vector<double> a2(4); a2[0]=1.0/3.0; a2[1]=1.0/5.0; a2[2]=1.0/7.0; a2[3]=1.0/9.0; //array<double,4> a3 ={3.14,2.72,4.62,4.41}; //array<double,4> a4; //a4 = a3; cout<<"a1[2]: "<<a1[2]<<" at "<<&a1[2]<<endl; cout<<"a2[2]: "<<a2[2]<<" at "<<&a2[2]<<endl; //cout<<"a3[2]: "<<a3[2]<<" at "<<&a3[2]<<endl; //cout<<"a4[2]: "<<a4[2]<<" at "<<&a4[2]<<endl; a1[-2]=20.2; cout<<"a1[-2]: "<<a1[-2]<<" at "<<&a1[-2]<<endl; //cout<<"a3[2]: "<<a3[2]<<" at "<<&a3[2]<<endl; //cout<<"a4[2]: "<<a4[2]<<" at "<<&a4[2]<<endl; }
数组,vector对象和array对象,都可使用标准数组表示法来访问各个元素。vector存储在堆,而数组和array存储在栈。array对象可以赋值给另外一个array对象,而对于数组,必须逐个复制数据
a1[-2]=20.2;
含义是*(a1-2) = 20.2;找到a1指向的地方,向前移动两个double元素,并将20.2存储到目的地,也就是说将信息存储到数组外面,与C语言一样,C++不检查这种超界错误。在这个实例中,这个位置至于array对象a3中。其他编译器可能放在a4中,甚至做出更糟糕的选择,这表明数组的行为是不安全的
44.
用enum定义一个名为Response的类型,它包含Yes=1,No=0,Maybe=2
enum Response {No,Yes,Maybe};
45.
编写一段代码,要求用户输入一个正整数,然后创建一个动态int数组,其中包含的元素数目等于用户输入的值。首先使用new来完成这个任务,再使用vector对象来完成这个任务。
unsigned int size; cout<<"Enter a positive integer: "; cin>>size; int * dyn = new int [size]; vector<int> dv(size);
46.
cout<<(int *) “Home of the jolly bytes”; 的含义是?
表达式”Home of the jolly bytes”是一个字符串常量,因此,它将判断为字符串开始的地址。cout对象将char地址解释为打印字符串,但类型(int *)将地址转换为int指针,然后作为地址被打印
47.
声明一个vector对象和一个array对象,它们都包含10个string对象,指出所需的头文件但不使用using,使用const来制定要包含的string对象
#include<vector> #include<array> #include<string> const int Str_num {10}; ... std::vector<std::string> vstr(Str_num); std::array<std::string,Str_num> astr;
48.
#include<iostream> const int Asize=20; using namespace std; struct student//定义结构描述 { char firstname[Asize]; char lastname[Asize]; char grade; int age; }; void display(student);//函数原型放在结构描述后 int main() { cout<<"what is your first name?"<<endl; student lcg;//创建结构变量(结构数据对象) cin.getline(lcg.firstname,Asize); cout<<"what is your last name?"<<endl; cin.getline(lcg.lastname,Asize); cout<<"what letter grade do you deserve?"<<endl; cin>>lcg.grade; cout<<"what is your age?"<<endl; cin>>lcg.age; display(lcg); return 0; } void display(student name) { cout<<"Name: "<<name.firstname<<","<<name.lastname<<endl; cout<<"Grade:"<<char(name.grade+1)<<endl; cout<<"Age:"<<name.age<<endl; }
49.
#include<iostream> #include<string> int main() { using namespace std; string name,dessert; cout<<"Enter your name: \n"; getline(cin,name); cout<<"Enter your favorite dessert: \n"; getline(cin,dessert); cout<<"I have some delicious "<<dessert; cout<<" for you, "<<name<<".\n"; return 0; }
字符串和getline( )的结合
50.
char数组实现名字拼接
#include<iostream> #include<cstring> const int Asize=20; int main() { using namespace std; char fname[Asize]; char lname[Asize]; char fullname[2*Asize+1]; cout<<"Enter your first name:";//输入名字,存储在fname[]数组中 cin.getline(fname,Asize); cout<<"Enter your last name:";//输入姓,存储在lname[]数组中 cin.getline(lname,Asize); strncpy(fullname,lname,Asize);//把姓lname复制到fullname空数组中 strcat(fullname,", ");//把“, ”附加到上述fullname尾部 strncat(fullname,fname,Asize);//把fname名字附加到上述fullname尾部 fullname[2*Asize]='\0';//为防止字符型数组溢出,在数组结尾添加结束符 cout<<"Here's the information in a single string:"<<fullname<<endl;//显示组合结果 return 0; }
string数组实现名字拼接
#include<iostream> #include<string> int main() { using namespace std; string fname,lname,attach,fullname; cout<<"Enter your first name:"; getline(cin,fname);//note:将一行输入读取到string类对象中使用的是getline(cin,str) //它没有使用句点表示法,所以不是类方法 cout<<"Enter your last name:"; getline(cin,lname); attach=", "; fullname=lname+attach+fname; cout<<"Here's the information in a single string:"<<fullname<<endl; return 0; }
51.
#include<iostream> const int Asize=20; struct CandyBar { char brand[Asize]; double weight; int calory; }; int main() { using namespace std; CandyBar snack[3]={ {"Mocha Munch",2.3,350}, {"XuFuJi",1.1,300}, {"Alps",0.4,100} }; for(int i=0;i<3;i++)//利用for循环来显示snack变量的内容 { cout<<snack[i].brand<<endl <<snack[i].weight<<endl <<snack[i].calory<<endl<<endl; } return 0; }
52.
#include<iostream> #include<string> const int Size=20; struct pizza//声明结构 { char company[Size]; double diameter; double weight; }; int main() { using namespace std; pizza *pie=new pizza;//使用new创建动态结构 cout<<"What's the diameter of pizza:"; cin>>pie->diameter; cin.get();//读取下一个字符 cout<<"What's the name of pizza company:"; cin.get(pie->company,Size); cout<<"What's the weight of pizza:"; cin>>pie->weight; cout<<"diameter:"<<pie->diameter<<" inches"<<endl; cout<<"company:"<<pie->company<<endl; cout<<"weight:"<<pie->weight<<" ounches"<<endl; delete pie;//delete释放内存 return 0; }
53.
#include<iostream> #include<string> using namespace std; struct CandyBar { string brand; double weight; int calory; }; int main() { CandyBar *snack= new CandyBar[3]; snack[0].brand="A";//单个初始化由new动态分配的内存 snack[0].weight=1.1; snack[0].calory=200; snack[1].brand="B"; snack[1].weight=2.2; snack[1].calory=400; snack[2].brand="C"; snack[2].weight=4.4; snack[2].calory=500; for(int i=0;i<3;i++) { cout << " brand: " << snack[i].brand << endl; cout << " weight: " << snack[i].weight << endl; cout << " calorie: " << snack[i].calory << endl<<endl; } delete [] snack; return 0; }
相关文章推荐
- c++内部类
- Socket编程(c语言示例)
- 重学C++Primer笔记3---一开始就要有好的习惯
- C++STL 编程技巧1 STL中各种排序算法的实现
- CUnit使用入门-精简的C语言单元测试工具
- 那些C++牛人的博客
- 初试java:java与c++的一些语法区别
- C++中基类的虚析构函数问题
- 单例模式的 模板方式实现 c++
- C++指针
- iOS学习笔记之-C语言基础02(关键字、标识符、注释、数据类型)
- C语言编程中碰到一个问题,求大神指教
- 学习ios第八天 番外篇 《c语言小游戏 推箱子的实现》
- 1.5 如何创建C++程序
- C++中map按value排序
- 1.4 C++程序的编写和实现
- iOS学习笔记之-C语言基础01
- 1.3 C++程序的构成和书写形式
- C语言中函数声明实现的位置
- 1.2 第一个C++程序