前言
本人在阅读C++ primer plus(第六版)的过程中做的笔记,写这篇文章既是为了分享,也是为了以后查阅。以下是本篇文章正式内容。
一、数组
- 声明数组的通用格式为typename arrayname[arraysize],arraysize必须是整型常数或者const值或者常量表达式,即arraysize的值在编译时是已知的而不能是变量;
- 将sizeof运算符用于数组名得到的是整个数组的长度,单位是字节;
- 只有在定义数组时才能进行初始化,不能将一个数组赋值给另一个数组;
- C++11中初始化数组时可省略等号,double earnings[4] {1.2e4, 1.6e4, 1.1e4, 1.7e4};
- 列表初始化禁止缩窄转换,缩窄转换的规则如下:
- 从浮点数转换为整数;
- 从取值范围大的浮点数转换为取值范围小的浮点数(在编译期可以计算并且不会溢出的表达式除外);
- 从整数转换为浮点数(在编译期可以计算并且不会溢出的表达式除外);
- 从取值范围大的整数转换为取值范围小的整数(在编译期可以计算并且不会溢出的表达式除外)
例如:
long plifs[] = {25, 92, 3.0}; //不允许浮点数转换为整数
char slifs[] = {‘h’, ‘i’, 12201, ‘\0’}; //不允许,12201超出char变量的取值范围
二、字符串
-
C风格字符串,
char dog[8] = {‘b’, ‘e’, ‘a’, ‘u’, ‘x’, ’ ', ‘I’, ‘l’}; //不是字符串
char cat[8] = {‘f’, ‘a’, ‘t’, ‘e’, ‘s’, ‘s’, ‘a’, ‘\0’}; //是字符串
空字符对C风格字符串至关重要,它们逐个处理字符串中的字符直到达到空字符为止; -
字符串常量或字符串字面值,
char bird[11] = “Mr.Cheeps”;
char fish[] = “Bubbles”;
用双引号扩起的字符串隐示地包含空字符; -
字符串常量(双引号扩起)不能与字符常量(单引号扩起)互换;
-
任何两个由空白分隔的字符串常量都将自动拼接为一个,下面的两个输出语句是等效的:
cout << “I’d give my right arm to be” “ a great violinist.\n”;
cout << “I’d give my right arm to be a great violinist.\n”; -
cstring头文件包含了函数strlen()以及很多字符串相关的其它函数,strlen()函数返回字符串的长度而不把空字符计算在内;
-
getline()函数读取整行,它使用通过回车键输入的换行符来确定结尾并且读取换行符,通过cin.getline()来调用;这个函数有两个参数,第一个参数输入字符数组的名称,第二个是读取的字符数,该函数在读取指定的字符数或遇到换行符时停止读取并在结尾自动添加空字符;
-
get()函数也是读取整行,它和getline()函数接受的参数以及参数解释相同,不同的是get()函数不再丢弃换行符而是将换行符留在输入队列中,
cin.get(name, Size); //没有问题
cin.get(dessert, Size); //出现问题
第一个get()函数留了一个换行符在输入队列中,导致第二个get()函数读取的第一个字符就是换行符而出现错误,解决的办法是在第一个get()函数后加上一个不带任何参数的get()函数,就像下面这样:
cin.get(name, Size);
cin.get();
cin.get(dessert, Size);
三、string类
-
可以使用数组表示法来访问存储在string对象中的字符;
-
C++11也允许将列表初始化用于string对象:
string cmd = {“the cmd”};
string cmo = {“the cmo”}; -
string类提供函数strcpy()将字符串(char数组,非string对象)复制到字符数组中,也提供函数strcat()将字符串(char数组,非string对象)附加到字符数组末尾:
strcpy(charr1, charr2); //将字符数组charr2复制到字符数组charr1中
strcat(charr1, charr2); //将字符数组charr2添加到字符数组charr1末尾 -
将一行输入读取到string对象中:getline(cin, str);//str是一个string类对象;
-
string对象不使用空字符来标记字符串结尾;
-
原始字符串
在原始字符串中,字符表示的就是自己,例如\n不再表示换行,而就是两个字符\和n,可以在字符串中使用"而不适用繁琐的";“和"不再表示字符串的开头和结尾,而是使用”(和)“来作界定符,在原始字符串中输入回车键不仅会移到下一行还会在原始字符串中添加回车字符,并使用R来表示原始字符串:
cout << R”(Jim “King” Tutt uses “\n” instead of endl.)" << “\n”;
上述代码将显示以下内容:
Jim “King” Tutt uses “\n” instead of endl.
自定义定界符可以替代默认定界符”(和)”,即在表示原始字符串开头的”和(之间添加其他字符,在结尾的)和”之间也要添加同样的字符来表示结尾,但空格、左括号、右括号、斜杠和控制字符(如制表符、换行符等)除外。例如:
cout << R”+(“(Who wouldn’t?)”, she whisperd.)+” << endl;
将显示如下:
“(Who wouldn’t?)”, she whisperd.
四、结构简介
- 将结构声明在函数外面可以供后面的所有函数使用,但声明在函数中只能供该函数使用;C++不提倡使用外部变量,但提倡外部结构声明;
- 结构的初始化
struct inflatable //结构声明
{
char name[20];
int number;
double price; //注意最后一个成员变量后有分号
}; //结构声明后的大括号后有分号
也可以全部放在一行中
inflatable guest = {“Glorious Gloria”, 5, 29.99};
或者省略等号
inflatable guest {“Glorious Gloria”, 5, 29.99};
如果大括号里不包含任何内容,则每个成员都被初始化为0;最后不允许缩窄转换; - 可以将结构作为参数传递给函数,也可以让函数返回一个结构,还可以使用赋值运算符将一个结构赋值给另一个同类型的结构;
- 可以同时完成结构的声明和创建结构变量的工作,只需将结构变量放在结束的括号后即可:
struct perks
{
int key_number;
char car[12];
}mr_smith, ms_jane; - 结构数组,要初始化结构数组可以结合使用数组初始化规则和结构初始化规则,
inflatable guests[2] =
{
{“Glorious Gloria”, 5, 2.66},
{“Godzilla”, 2000, 565.99}
};
五、共用体
共用体能够存储不同的数据类型,但只能同时存储其中的一种,例如结构可以同时存储int、double和long类型,而共用体只能存储int、double或long;共用体的句法与结构相似但是含义不同。例如下面的声明:
union one4all
{
int int_val;
long long_val;
double double_val;
};
one4all pail; //创建一个union对象
pail.int_val = 15; //存储一个整数
pail.double = 2.33; //存储一个浮点数,整数失效
共用体每次只能存储一个值,所以共用体的长度就是最大成员的长度,其用法如下:
struct widget
{
char brand[20];
int type;
union id
{
long id_num;
char id_char[20];
}id_val;
};
…
widget prize;
…
if (type == 1)
{
cin >> prize.id_val.id_num;
}
else
{
cin >> prize.id_val.id_char;
}
共用体可以匿名:
struct widget
{
char brand[20];
int type;
union
{
long id_num;
char id_char[20];
};
};
…
widget prize;
…
if (type == 1)
{
cin >> prize.id_num;
}
else
{
cin >> prize.id_char;
}
由于共用体是匿名的,id_num和id_char都被视为prize的两个成员变量,他们的地址相同。
六、枚举
- C++的enum工具提供了一种创建符号常量的方式,这种方式可以替代const。使用enum的句法与使用结构相似,
enum spectrum {red, orange, yellow, green, blue, violet, indigo, ultraviolet};
这条语句完成两项工作:1、让spectrum成为新类型的名称,称为枚举;2、将red、orange、yellow等作为符号常量,称为枚举量。在默认情况下将整数值赋给枚举量,第一个枚举量的值为0,第二个为1,以此类推。 - 可以用枚举名来声明这种类型的变量:
spectrum band; //band是枚举类型的变量 - 在不进行强制类型转换的情况下只能将定义枚举时使用的枚举量赋给枚举类型的变量:
band = blue; //可以
band = 2000; //不可以,2000不是枚举量 - 枚举量是整型,可以将枚举量提升为int类型,但不能将int类型自动转换为枚举类型;
- 如果int值是有效的,则可以通过强制类型转换将它赋给枚举变量:band = spectrum(3);
- 如果只使用常量而不创建枚举变量,则可以省略枚举类型的名称:
enum {red, orange, yellow, green, blue, violet, indigo, ultraviolet}; - 可以使用赋值运算符来显式地设置枚举量的值,指定的值必须是整数:
enum bits {one = 1, two = 2, four = 4, eight = 8};
也可以只显示地设置其中一些变量的值:
enum bigstep {first, second = 100, third};
这时候first被默认为0,后面没被初始化的枚举量的值比其前一个值大1,所以third值为101; - 可以创建多个值相同的枚举量;
- 枚举的取值范围:在2的幂次方中找到比声明枚举时枚举量的最大值大的最小值,再减1得到的就是取值范围的上限,例如在enum bits {one = 1, two = 2, four = 4, eight = 8};中,声明枚举时枚举量的最大值是8,2的4次方16是比8大的最小值,因此取值范围上限是15;要找到下限,需要知道声明时枚举量的最小值,如果不小于0,则取值范围下限为0,如果小于0则按照与找上限相同的方法,但加上负号,如果声明时最小值是-8,则下限是-15。
七、指针和自由存储空间
- 下面的声明创建一个指针(p1)和一个int变量(p2),对每个指针变量名都需要使用一个*:
int *p1, p2; - 一定要在对指针使用解除引用运算符(*)之前将指针初始化为一个确定的、适当的地址;
- 指针不是整型,虽然计算机通常把指针当作整数来处理,但是不能简单地把整型赋值给指针,但是可以通过强制类型转换将数字转换为地址类型:
int *pt;
pt = 0xB8000000; //错误
pt = (int *)0xB8000000; //正确 - new运算符在运行阶段分配未命名的内存以储存值,程序员需要指定为哪种数据类型分配内存,new会找到一个长度正确的内存块并返回该内存块的地址:
int *pn = new int; - 用new运算符为一个数据对象(可以是结构,也可以是基本数据类型)分配内存的通用格式如下:
typeName *pointerName = new typeName; - 常规变量的存储区域在栈(stack)中,而new分配的内存在堆(heap)中或者自由存储区(free store)中;
- 使用delete运算符释放内存,
int *ps = new int;
…
delete ps;
这将释放ps指向的内存,但不会删除指针ps本身,可以将ps指向另一个新分配的内存块;一定要配对使用new 和delete; - 静态联编和动态联编
在编译时给数组分配内存称为静态联编;使用new时,如果在运行阶段需要数组则创建它,如果不需要则不创建,还可以在程序运行时选择数组长度,这叫动态联编; - 数组和指针基本等价,但又有根本的差别,见下面代码:
#include<iostream>
using namespace std;
int main()
{
double *dpnewArray = new double[3]; //使用new动态创建一个double数组
dpnewArray[0] = 0.1; //给数组元素赋值,这里指针和数组名等价
dpnewArray[1] = 0.2;
dpnewArray[2] = 0.3;
cout << "dpnewArray[1] = " << dpnewArray[1] << endl;
dpnewArray = dpnewArray + 1; //指针加1,数组名不能进行加减
cout << "dpnewArray + 1 后dpnewArray[0] = " << dpnewArray[0] << endl;
dpnewArray = dpnewArray - 1; //指针减1保证释放内存时能释放正确的位置
delete[] dpnewArray; //释放已分配的内存
cin.get();
return 0;
}
运行结果如下:
- 将指针变量加1后,增加的量等于它指向的类型的字节数;C++将arrayname[i]解释为*(arrayname + i);对数组名应用sizeof运算符得到的是整个数组的长度,对指针应用则得到指针的长度,即使指针指向的是一个数组;
- C++将数组名视为数组第一个元素的地址,一个例外是,对数组名应用sizeof运算符得到的将是整个数组的长度(单位为字节);
- 分析下面代码:
int tacos[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int* pf = tacos;
pf = pf + 1; //pf的值是tacos[1]的地址
int* pl = &tacos[9];
pl = pl – 1; //pl的值是tacos[8]的地址
int diff = pl – pf; //diff的值是7,而不是28,似乎它会自动转换为相隔元素的个数?
- 在C++中,char数组名、char指针以及用引号扩起的字符串常量都被解释为字符串中第一个字符的地址;
- 一般来说,给cout提供一个指针,它将打印地址,但如果是char指针,则cout将显示指向的字符串;如果要显示字符串的地址,则必须将char指针强制转换为另一种指针类型:
char *pstr = “hello”;
cout << pstr; //该语句将打印字符串
cout << (int *)pstr; //该语句将打印字符串的地址 - 经常需要将字符串放到数组中,初始化数组时应使用"="运算符,否则应使用strcpy()函数;
- 高版本的C++对一些老式的函数警告为不安全,这时候可以使用宏指令忽略这种警告:
#ifdef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif
new和delete放在代码的不同地方也是可以的,比如可以放在不同的函数中,见下面代码:
#include<iostream>
#include<cstring>
#ifndef _CRT_SECURE_NO_WARNINGS //如果没有定义这个宏
#define _CRT_SECURE_NO_WARNINGS //则定义这个宏
#endif //条件判断结束
//#define _CRT_SECURE_NO_WARNINGS //该环境认为下面要使用的strcpy()函数不够安全,这条宏旨在忽略这种警告
using namespace std;
char *getname(); //声明一个返回字符串地址的函数
int main()
{
cout << "输入字符串:";
char *strname;
strname = getname();
cout << "字符串:" << strname << ",地址:" << (int *)strname << endl;
delete[] strname; //释放空间
cin.get();
return 0;
}
char *getname()
{
char temp[100]; //临时数组
cin.getline(temp, 100);
char *name = new char[strlen(temp) + 1]; //申请空间,存放输入的数组,以返回给函数
strcpy(name, temp);
return name;
}
- 自动存储、静态存储和动态存储
- 在函数内部定义的常规变量通常被称为自动变量,自动变量通常存储在栈中,按先进后出的方式存储和释放,这表明其内存空间是连续的;
- 静态存储是在整个程序生命周期都存在的存储方式,使变量成为静态的方式有两种,一种是在函数外面声明,一种是声明变量时使用关键字static,static double fee = 5.0;
- 由new和delete运算符分配和释放的存储方式是动态存储,使用的内存空间在堆或者自由存储区中。
八、类型组合
分析下面代码:
#include <iostream>
int main()
{
//创建一个结构
struct strutYear
{
int year;
}s0, s1, s2;
s0.year = 1998;
s1.year = 1888;
s2.year = 1999;
//创建一个结构数组
strutYear aio[3] = { s0, s1, s2 };
//创建一个指针数组,数组的每个元素都是一个指向结构的指针
strutYear *arp[3] = { &s0, &s1, &s2 };
std::cout << "arp[0].year = " << arp[0]->year << std::endl;
std::cout << "arp[1].year = " << (*(arp + 1))->year << std::endl;
//创建一个指向指针的指针
strutYear **ppa = arp; //arp和ppa的值相等
std::cout << "arp[2].year = " << (*(ppa + 2))->year << std::endl;
std::cout << "arp: " << arp << std::endl;
std::cout << "ppa: " << ppa << std::endl;
std::cout << "&arp: " << &arp << std::endl;
std::cout << "&ppa: " << &ppa << std::endl;
std::cin.get();
return 0;
}
下面是运行结果:
九、数组的替代品
- 模板类vector
要使用vector对象就要包含头文件vector和使用名称空间std;
下面的语句声明一个名为vt的vector对象,其包含n_elem个类型为typeName的元素:
vector<typeName> vt(n_elem);
其中参数n_elem可以是整型常量也可以是整型变量。 - 模板类array
要使用array对象就要包含头文件vector和使用名称空间std;
下面的语句声明一个名为arr的array对象,包含n_elem个类型为typeName的元素:
array<typeName, n_elem> arr;
要注意n_elem只能是整型常量不能是变量。 - 可以使用vector和array的成员函数at()来访问其元素:
vector<double> ar(3);
cin >> ar.at(2);
总结
以上就是本篇文章的全部内容,记录了数组、字符串、string类、结构体、共用体、枚举、指针和自由存储空间、类型组合和数组的替代品。