基于基本类型和浮点类型创建(C语言:派生类型)
1.数组(简介)
1.数组声明 ·存储在每个元素中的值的类型 ·数组名 ·数组中的元素量
typeName arrayName[arraySize];
2.数组初始化 C++11初始化 ·可省略等号·大括号不包含任何东西所有元素置为零·列表初始化禁止窄缩转换
3.C++标准模板库(STL)提供了一种数组代替品——模板类vector
2.字符串
C++处理字符串的方式有两种,第一种来自C语言,C-风格字符串,另一种基于string类库的方法
1.初始化 ·数组初始化 '\0' ·字符串常量(字符串字面值)
2.处理字符串的函数根据空字符的位置,而不是数组长度来进行处理,C++对字符串长度没有限制
sizeof运算符指出整个数组的长度,strlen()函数返回的是存储在数组中的字符串的长度,而不是数组本身的长度,此外,strlen()只计算可见的字符串,不把空字符计算在内
3. cin使用空白来确定字符串的结束位置,这意味着cin 在获取字符串输入时只读取一个单词 (cin的高级特性)
4.面向行的输入:getline()
istream类,getline()函数读取整行,它使用通过回车键输入的换行符来确定输入结尾
调用: cin.getline()
参数:有两个参数,第一个参数用来存储输入行的数组的名称,第二个参数是要读取的字符数。getline成员函数在读取指定数目的字符或遇到换行符时停止读取
getline()函数每次读取一行,它通过换行符来确定行尾,但不保存换行符。相反,在存储字符串时,它用空字符来替换换行符
5.面向行的输入:get()
istream类,该函数有几种变体,其中一种变体的工作方式与getline相似,接受的参数相同,解释参数的方式也相同,但是不在读取并丢弃换行符,而是将其留在输入队列中。如果不使用帮助get()不能跨过该换行符
另一种变体,不带参数的cin.get(),调用读取下一个字符(处理换行符)
另一种使用方式是将两个类成员函数拼接起来(合并)
cin.get(name. Arsize).get();
cin.get(name, Arsize).getline(name2, Arsize);
cin.get(name, Arsize)返回一个cin对象,该对象随后将被用来调用get()函数
C++允许函数有多个版本,条件是这些版本的参数列表不同——函数重载
6.老式实现没有getline(),get()使输入更仔细,例如,假设用get()将一行读入数组中,如何知道停止读取的原因是 由于已经读取了整行,而不是由于数组已填满。查看下一个输入字符,如果是换行符,说明已经读取了整行,否则,说明该行中还有其他输入。(17章updateing)getline()使用简单,但get()使得检查错误更简单些
7.空行和其他问题(5章,6章, 17章updating) p81
当getline()或get()读取空行时,将发生什么
当get()读取空行后将设置失效位(failbit)这意味着接下来的输入将被阻断,但可以用cin.clear()恢复输入
另一个问题是,输入行包含的字符数比指定的多,getline()和get()将把余下的字符留在输入队列之中,而getline()还会设置失效位,并关闭后面的输入
8.混合输入字符串和数字
混合输入数字和面向行的字符串会导致问题,读取数字后,回车键生成的换行符留在了输入队列中,读取面向行的字符串前应先读取并丢弃换行符
cin.get();
cin.get(ch);
3.String类 (简介)
使用string类的对象来存储字符串,string类使用比数组简单,提供了将字符串作为一种数据类型的表示方法。使用string类要在头文件中包含头文件string,string类位于名称空间std中
1.与C-风格字符串异同
·可以使用C-风格字符串来初始化string对象
·可以使用cin将键盘输入存储到string对象中,cout来显示string对象
·可以使用数组表示法来访问存储法来访问存储在string对象中的字符
·最主要区别是,可以将string对象声明为简单变量,而不是数组,类设计让程序可以自动处理string的大小。这使得与使用数组相比,使用string对象更方便,也更安全。从理论上说,可以将char数组视为一组用于存储一个字符串的char存储单元,而string类变量是一个表示字符串的实体
2.C++11字符串初始化
允许将列表初始化用于C-风格字符串和string对象
string str1 {"Hello World!"};
3.string类的操作
不能将一个数组赋给另一个数组,但是可以将一个string对象赋给另一个string对象
可使用+/+=拼接两个string对象
C-风格字符串 头文件cstring提供C语言库中的函数
strcpy(charr1, charr2); //copy charr2 to charr1
strcat(charr1, charr2); //append contents of charr2 to char1
strncpy(charr1, charr2, n);
strncat(charr1, charr2, n);
后两个函数接收指出目标数组最大允许长度的第三个参数,因此更为安全
string类具有自动调整大小的功能,能够避免覆盖相邻内存,数据破坏的事情发生
string str1;
char charr[20];
int len1 = str1.size(); //类方法,方法是一个函数,只能通过其所属类的对象进行调用
int len2 = strlen(charr1); //常规函数,接收一个C-风格字符串为参数,返回字符数
4.string类I/O
未初始化的数组的内容是未定义的,未被初始化的string对象的长度被自动设置为零。
cin.getline(charr, 20);
getline(cin, str1); /*不是一个类方法,istream中有处理double、int和其他基本类型的类方法,但没有处理string对象的类方法。将cin作为参数,指出到哪里去查找输入。没有指出字符串长度的参数,因为string对象根据字符串的长度自动调整自己的大小。*/
cin >> x; //使用istream类的一个成员函数
cin >> str; //使用string类的一个友元函数 (updating)
5.其他形式的字符串字面值
wchar_t title[] = L''Chief Astrogator"; //wchar前缀 L
char16_t name[] = u"Felonia Ripova"; //char16_t前缀 u
char32_t car[] = U"Humber Super Snipe"; // char32_t前缀 U
(unfinshed)
4.结构(简介)
结构是一种比数组更灵活的数据格式,因为同一个结构可以存储多种类型的数据。
结构是用户定义的类型,而结构声明定义了这种类型的数据属性。定义了类型后,便可以创建这种类型的变量。创建结构包括两步,首先,定义结构描述——它描述并标记了能够存储在结构中的各种数据类型。然后按照描述创建结构变量(结构数据对象)。
struct inflatable
{
char name[]; //结构的成员 , 声明语句
float volume;
double price;
};
inflatable hat; //inflatable类型的变量
C++允许在声明结构变量时省略关键字struct,在C++中结构标记的用法与基本类型名相同。访问成员函数的方式是从访问结构成员变量(eg : hat.volume)的方式衍生而来的。
C++不提倡使用外部变量,但提倡使用外部结构声明,另外,在外部声明符号变量通常更合理
1.初始化
和数组一样,使用由逗号分割值列表,并将这些值用花括号扩起。
C++结构初始化 等号可选,其次,如果大括号内未包含任何东西,各个成员都将被设置为零,C-风格字符串每个字节都被设置为零,最后,不允许缩窄转换
如果编译器支持对以string对象作为成员的结构进行初始化,可以使用string对象而不是字符数组(std::string str)
2.其他结构属性
C++使用户自定义的类型与内置类型尽可能相似。
可以将结构作为参数传递给函数,可以返回一个结构。
可以使用赋值运算符将结构赋给另一个同类型的结构,即使成员是数组,这种赋值被称为成员赋值
(传递返回结构)
可以同时完成定义结构和创建结构变量的工作,只需将变量名放在结束括号的后面即可。
struct inflatable
{
char name[];
float volume;
double price;
}hat, key; //two variable
可以初始化这样创建的变量
struct inflatable
{
char name[];
float volume;
double price;
}hat =
{
"Key",
6.0,
8.7,
};
还可以声明没有名称的结构类型,方法是省略名称,同时定义一种结构类型和一个这种类型的变量。
struct
{
char name[];
float volume;
double price;
} key;
这样将创建一个名为key的结构变量,可以使用成员运算符来访问它的成员,但这种类型没有名称,因此以后无法创建这种类型的变量。
与C结构不同,C++结构除了成员变量之外,还可以有成员函数,但这些高级特性通常被用于类中,而不是结构中。(Updating)
3.结构数组
可以创建元素为结构的数组
初始化:结合初始化数组和初始化结构的规则
4.结构中的位字段
与C语言一样,C++也允许指定占用特定位数的结构成员,这使得创建与某个硬件设备上的寄存器对应的数据结构非常方便。字段的类型应为整型或枚举。冒号后面是一个整数,指定了使用的位数。可以使用没有名称的字段来提供间距,每个成员都被称为位字段。
struct torgle
{
unsigned int SN : 4;
unsigned int : 4; //4bits unused
bool goodIn : 1;
}
可使用标准的结构表示法来访问位字段。
位字段通常用在低级编程中,一般来说,可以使用整型和按位运算符来代替这种方式。(附录E updating)
5.共用体
共用体(union)是一种数据格式,它能存储不同的数据类型,但只能同时存储其中的一种类型。
union one
{
int int_val ;
long long_val;
double double_val;
};
one pail;
pail.int_val = 35;
cout << pail.int_val;
pail.double_val = 98.75;
cout<<pail.double_val;
共用体的长度为其最大成员的长度,共用体的用途之一是,当数据项使用两种或更多格式但不会同时使用时,可节省空间。
匿名共用体没有名称,其成员将成为位于相同地址处的变量
struct widget
{
char brand[];
int type;
union
{
long id_num;
double id_char[];
};
};
widget prize;
if(prize.type == 1)
cin>>prize.id_num;
else
cin>>prize.id_char;
共用体常用于节省内存,此外,共用体常用于操作系统数据结构或硬件数据结构。
6.枚举
C++的enum提供了另一种创建符号常量的方式,这种方式可以代替const,它还允许定义新类型,但必须按严格的限制执行。
enum spectrum {red, orange, yellow, greeen, blue, violet, indigo, ultraviolet};
这条语句中,spectrum成为新类型的名称:枚举,将red,yellow等作为符号常量,他们对应整数值0-7,这些常量叫做枚举量。在默认的情况下,将整数值赋给枚举量,第一个枚举量的值为0,第二个为1,以此类推可以通过显式的指定整数值来覆盖默认值,在不进行强制性类型转化的情况下,只能将定义枚举时使用的枚举量赋给这种枚举的变量。spectrum变量只有八个可能的值,为了获得最大程度的可移植性,应将把非enmu值赋给enum变量视为错误(有些编译器出现错误,有些编译器警告)。
对于枚举,只定义了赋值运算符,没有为枚举定义算术运算。(有些实现没有这种限制,这有可能导致反类型限制)
spectrum band;
band = blue;
枚举量是整型,可被提升为int类型,但int类型不能自动转化为枚举类型。(可将枚举赋给int,但不能把int类型赋给枚举类型,有些实现没有这种限制)枚举可以转换为int类型,因此可以在算数表达式中同时使用枚举和常规整数,尽管并没有为枚举本身定义算数运算。
band = orange + red; // not valid. 相当于将一个int类型赋给枚举量
如果int值有效,则可以通过强制类型转换,将它赋给枚举变量:
band = spectrum(3);
如果将一个不适当的值进行强制类型转换,结果是不确定的,这么做不会出错,但结果不可用。
枚举常被用来定义相关的符号常量,而不是新的类型,如果只打算使用常量,而不创建枚举类型的变量,则可省略枚举类型的名称
enum{ red, orange, yellow, greeen, blue, violet, indigo, ultraviolet }
1.设置枚举量的值
可以使用赋值运算符来显式地设置枚举量的值,指定的值必须是整数,也可以只显式地定义其中一些枚举量的值,后面没有被初始化的枚举量的值将比其前面的枚举量大1,可以创建多个值相同的枚举量。在C++早期版本中只能将int值(或提升成int的值)赋给枚举量,但这种限制取消了,因此可以使用long甚至longlong类型的值。
2.枚举的取值范围
最初对于枚举来说,只有声明中指出的那些值是有效的,然而,C++现在通过强制类型转换,增加了可赋给枚举变量的合法值。每个枚举变量都有取值范围(range),通过强制类型转换,可以将取值范围内的任何整数值赋给枚举变量,即使这个值不是枚举值。
取值范围如下
上限:找到枚举量的最大值,找到大于这个最大值的、最小的2的幂,将它减去1,得到的便是取值范围的上限
下限:找到枚举量的最小值,如果它不小于0,则取值下限为0;否则,与上限方式相同,但加上负号;
选择用多少空间来存储枚举由编译器决定,对于取值范围较小的枚举,使用一个字节或更小的空间,而对于包含long类型值的枚举,则使用4个字节。
3.
C++11扩展了枚举,增加了作用域内枚举
7.指针(指针、数组、自由存储空间)
(此部分内容整合进C++专项中,C++数组和C++指针。)
计算机程序在存储数据中必须跟踪的三种基本属性:
·信息存储在何处
·存储的值为多少
·存储的值是什么类型
定义一个简单变量时,声明语句指出了值的类型和符号名,还让程序为值分配内存,并在内部跟踪该内存单元。使用常规变量时,值是指定的量,而地址为派生量。使用指针策略时,将地址视为指定的量,而将值视为派生量。指针是C++内存管理编程理念的核心。
指针与C++基本原理: 面向对象编程与传统的过程性编程的区别在于,OOP强调的是在运行阶段而不是编译阶段做决策。运行阶段决策提供了灵活性。在运行阶段做决策并非OOP独有的,但使用C++编写这样的代码比使用C简单。
例如:为数组分配内存时,声明数组,指定数组长度为编译阶段决策;使用OOP时,在运行阶段决定数组的长度。为使用这种方法,语言必须允许在程序运行时创建数组,C++采用的方法是,使用关键字new请求正确数量的内存以及使用指针跟踪新分配的内存的位置。
1.声明和初始化指针
typename * pointerName;
int* ptr; //在C++中,int*是一种类型——指向int的指针
int* ptr1, ptr2; //创建一个指针,和一个int变量。对每个指针变量名,都需要使用一个*
和数组一样,指针都是基于其他类型的
在声明语句中初始化指针,被初始化的时指针,而不是它指向的值
int higgens = 5;
int* ptr = &higgens;
指针的危险: 在C++中创建指针时,计算机将分配用来储存地址的内存,但不会分配用来存储指针所指向的数据的内存。使用指针时,先初始化,再解引用。
指针不是整型,虽然计算机通常把地址当作整数来处理。不能简单地将整数赋给指针,要将数字值作为地址来使用,应通过强制类型转换将数字转换为适当的地址类型:
int* ptr;
ptr = (int*) 0xB8000000;
ptr是int值的地址并不意味着ptr本身的类型是int
2.使用new来分配内存(使用指针来管理运行阶段的内存空间分配)
指针真正的用武之地在于 : 在运行阶段分配未命名的内存以存储值。在这种情况下,只能通过指针来访问内存
在C语言中,可以用库函数malloc()来分配内存;在C++仍然可以这样做,但还有更好的方法——new运算符
在运行阶段为一个int值分配未命名的内存,并使用指针来访问这个值。要告诉new需要为哪种数据类型分配内存;new将找到一个长度正确的内存块,并返回其地址,将该地址赋给一个指针。
typeName* pointer_name = new typeName; //为一个数据对象获得并指定分配内存的通用格式
该pointer指向一个数据对象,"数据对象"比"变量"更通用,它指的是为数据项分配的内存块。
处理数据对象的指针方法使程序在管理内存方面有更大的控制权。
new分配的内存块通常与常规变量声明分配的内存块不同,常规声明的变量都存储在被称为栈(stack)的内存区域中;而new从被称为堆(heap)或自由存储区(free store)的内存区域分配内存。
计算机可能会由于没有足够的内存而无法满足new的请求,这种情况下,new通常会引发异常(错误处理),在较老的实现中,new将返回0. 在C++中,值为0的指针被称为空指针(null pointer)。C++确保空指针不会指向任何有效的数据,因此它常被用来表示运算符或函数失败,如果成功,将返回一个有用的指针。可检测处理分配内存失败。
3.使用delete释放内存
当需要内存时,可以使用new来请求,这只是C++内存管理数据包中有魅力的一个方面,另一个方面是delete运算符,它使得在使用完内存后,能够将其归还给内存池这是通向最有效的使用内存的关键一步。归还获释放的内存可供程序的其他部分使用。
in* ps = new int;
delete ps;
· 这将释放ps指向的内存,但不会删除指针ps本身。
·要配对的使用new和delete,否则将发生内存泄漏,也就是说,被分配的内存再也无法使用了。如果内存泄漏严重,则程序将由于不断寻找更多内存而终止。
·不要释放已经释放的内存块,C++标准指出,这样做的结果是不确定的,这意味着什么情况都有可能发生。
·不能使用delete来释放声明变量所获得的内存
·只能用delete来释放使用new分配的内存。然而,对空指针使用delete是安全的
·使用delete的关键在于,将它用于new分配的内存。这并不意味着要使用用于new的指针,而是用于new的地址
·不要创建两个指向同一个内存块的指针,这将增加错误地删除同一个内存块两次的可能性
4.使用new来创建动态数组
通常,对于大型数据(如数组,字符串和结构),应使用new。
编译时给数组分配内存被称为静态联编,但使用new时,如果在运行阶段需要数组,则创建它,如果不需要,则不创建。还可以在程序运行时选择数组的长度,这被称为动态联编。这种数组被称为动态数组。
1.使用new创建动态数组
int* psome = new int[100];
new运算符返回第一个元素的地址
当程序使用完new分配的内存块时,应使用delete释放它们
delete[] psome; //格式匹配
psome是指向一个int的指针,必须让程序跟踪内存块中的元素个数,以便以后使用delete[]运算符时能正确的释放这些内存。但这些信息不是公用的,例如,不能使用sizeof运算符来确定动态分配的数组包含的字节数
type_name * pointer_name = new type_name [num_elements]; //通用格式
2.使用动态数组
将指针名当作数组使用
C和C++内部都用指针来处理数组
不能修改数组名的值,但是指针是变量,因此可以修改它的值。
5.指针、数组和指针算数
指针和数组基本等价的原因在于指针算数(pointer-arithmetic)和C++内部处理数组的方式。
在多数情况下,C++将数组名解释为数组第一个元素的地址。
指针变量加1,其增加的值等于指向的类型占用的字节数
通常,使用数组表示法(包括使用指针),C++将执行以下转换:
arrayname[i] becomes *(arrayname + i)
pointername[i] becomes *(arrayname + i)
因此,在很多情况下,可以相同的方式使用指针名和数组名。对于它们,可以使用数组方括号表示法,也可以使用解除引用运算符。在多数表达式中,它们都表示地址。区别之一是,可以修改指针的值,而数组名是常量。另一个区别是,对数组应用sizeof运算符得到的是数组的长度,而对指针应用sizeof得到的是指针的长度,即使指针指向的是一个数组,这种情况下,C++不会将数组名解释为地址。
数组的地址: 数组名被解释为其第一个元素的地址,而对数组名应用地址运算符时,得到的是整个数组的地址。
int tell[10];
cout << tell << endl; //displays &tell[0]
cout << &tell << endl; // displays address of whole array
int (*pas)[10] = &tell; //pas points to array of 10 int
int* pas[10] //an array of pointer to int
1.对指针解除引用
对指针解除引用意味着获得指针指向的值,对指针应用间接值运算符,另一种对指针解除引用的方法是使用数组表示法,千万不能对未被初始化为适当地址的指针解除引用。
2.指针算数
C++允许将指针和整数相加,加1的结果等于原来的地址值加上指向的对象占用的总字节数。还可以将一个指针减去另一个指针,获得两个指针的差。仅当两个指针指向同一个数组时,这种运算才有意义。
3.数组的动态联编和静态联编
静态联编:使用数组声明来创建数组,即数组的长度在编译时设置
动态联编:使用new[]运算符创建数组,运行时分配空间,其长度也在运行时设置。使用完这种数组,应使用delete[]释放其占用的内存。
4.数字表示法和指针表示法
6.指针和字符串
1.
数组和指针的特殊关系可以扩展到C-风格字符串
在C++中,用引号括起的字符串像数组名一样,也是第一个元素的地址
对于数组中的字符串,用引号括起的字符串常量以及指针所描述的字符串,处理的方式是一样的,都将传递它们的地址
字符串字面值是常量,有些编译器将字符串字面值视为只读常量,可以访问,但不能修改,如果试图修改,将导致运行阶段错误。最好使用const关键字(const指针)
有些编译器只使用字符串字面值的一个副本来表示程序中所有的该字面值,C++不能保证字符串字面值被唯一的存储。如果是以上情况,将值读入一个字符串,可能会影响被认为是独立的为位于其他地方的字符串。
在将字符串读入程序时,应使用已分配的内存地址。该地址可以是数组名,也可以是new初始化的指针,不要使用字符串常量或未被初始化的指针来接收输入。为避免这些问题,可以使用一个足够大的char数组或使用std::string对象
2.如何获得字符串的副本
首先,需要分配内存来存储该字符串,可以通过声明另一个数组或者使用new来完成。
ps = new char[strlen(array) + 1]; //不会浪费空间
不能将array直接赋给ps,这样只能修改存储在ps中的地址,从而失去程序访问新分配内存的唯一途径,需要使用库函数strcpy():
strcpy(ps, array);
获得array的两个独立副本,new在离animal数组很远的地方找到了所需的内存空间。
strcpy/strncpy
7.使用new创建动态结构
在运行时创建数组优于在编译时创建数组,对于结构也是如此。通过使用new,可以创建动态结构
将new用于结构由两步构成:创建结构和访问其成员。
创建动态结构时,不能将成员运算符句点用于结构名,因为这种结构没有名称,只知道它的地址。
C++专门为这种情况提供了一个运算符:箭头成员运算符(->),可用于指向结构的指针。
另一种访问结构成员的方法是,如果ps是指向结构的指针,则*ps就是被指向的值——结构本身,因此(*ps).price是该结构的price成员。C++的运算符优先规则要求使用括号。
8.自动存储、静态存储和动态存储
根据用于分配内存的方法,C++有三种管理数据内存的方式:自动存储、静态存储和动态存储(自由存储存储空间、堆),C++11新增了第四种类型——线程存储。(9章)
1.自动存储
在函数内部定义的常规变量使用自动存储空间,被称为自动变量,这意味着它们在所属的函数被调用的时候产生,在该函数结束是消亡。实际上,自动变量是一个局部变量,其作用域为包含它的代码块。
自动变量通常存储在栈中。这意味着执行代码块时,其中的变量将依次加入到栈中,而在离开代码块时,将按相反的顺序释放这些变量,这被称为先进后出。因此,在程序执行的过程中,栈将不断的增大和缩小。
2.静态存储
静态存储是整个程序执行期间都存在的存储方式。是变量成为静态的方式由两种:一种是在函数外面定义它;另一种是使用关键字static。
在K&RC中,只能初始化静态数组和静态结构,而C++Release2.0及后续版本和ANSIC中,也可以初始化自动数组和自动结构。然而,有些C++实现还不支持对自动数组和自动结构的初始化。
自动存储和静态存储的关键在于,这些方法严格地限制了变量的寿命。
3.动态存储
new和delete提供了一种比自动和静态变量更灵活的方法。它们管理了一个内存池,这在C++中被成为自由存储空间或堆。该内存池同用于静态变量和自动变量的内存是分开的。数据的声明周期不完全受程序或函数的生存时间控制。与使用常规变量相比,使用new和delete让程序员对程序如何使用内存有更大的控制权。然而,内存管理也更加复杂了。在栈中,自动添加和删除机制使得占用的内存总是连续的,但new和delete的相互影响可能导致占用的自由存储区不连续,这使得跟踪新分配内存的位置更困难。
4.栈、堆和内存泄漏
8.类型组合
9.数组的代替品
模板类vector/array(C++11)