英文原版:https://github.com/google/styleguide
中文版:https://github.com/zh-google-styleguide/zh-google-styleguide
其中C++ 规范5 万多字,过于庞大
https://geosoft.no/development/cppstyle.html
4.9 版本有94 条规则。规则相对简明
类是一种 抽象 的 自定义 数据类型,它并 不占用 内存空间
所以不能在类内对 数据成员 进行 初始化
这点,以后有时间从类加载机制上,理解。不要局限在记住这些规定,这些是可以通过原理解释的
先加载类
还有了解到,程序执行(或者说编译,应该就是说执行时),并不是main函数先加载执行,而是先加载类信息(体会、理解一下)
class Box {
...
private:
int length = 0; //错误,不能在类内对数据成员进行初始化
}
对数据成员 初始化 的方法:
1、在 构造函数 的 函数体 内通过 赋值语句 对 数据成员 进行 初始化
2、参数初始化表
class Box{
public:
// 使用参数初始化表的构造函数
// 就是: 后面中,(L)中的L,赋给length,(W)中的W,赋给width, (H)中的H,赋给height
Box(float L, float W, float H) : length(L), Width(W), heigth(H) {}
float Volume() {
return length * width * height;
}
private:
float length, width, height;
}
1)在全局范围中定义的对象(即在函数之外定义的对象),
其 构造函数 在文件中的 所有函数(包括 main
函数)执行之前调用
但如果一个程序中有 多个文件,而不同的文件中都定义了 全局对象,则这些对象的 构造函数 的 执行顺序 是 不确定 的
当 main
函数执行完毕 或 调动 exit()
函数时(此时程序终止),调用 析构函数
2)如果定义的是局部自动对象(如在函数中定义对象),
则在创建对象时调用其 构造函数。
如果程序被 多次 调用,则在每次创建对象时,都要调用 构造函数
在函数调用结束、释放对象时,先调用析构函数
3)如果在函数中定义 静态(static
)局部对象,
则只在程序第一次调用此函数创建对象时,调用构造函数一次,在调用结束时,队形并不释放,
因此也不调用析构函数,只在 main
函数结束或调用 exit()
函数结束程序时,才调用析构函数
对象数组初始化的几种方法
可以说,只有一种初始化方法,完整初始化赋值
Box box[3] = { {1, 3, 5}, {2, 4, 6}, {3, 6, 9} };
#include
using namespace std;
class Box {
private:
float length, width, height;
public:
Box() {
length = 1; width = 1; height = 1;
cout << "Box(" << length << "," << width << "," << height << ")的构造函数,被调用" << endl;
}
Box(float L, float W, float H) {
length = L; width = W; height = H;
cout << "Box(" << length << "," << width << "," << height << ")的构造函数,被调用" << endl;
}
};
int main() {
Box box[3] = { Box(1, 3, 5), Box(2, 4, 6), Box(3, 6, 9) };
// 1.经测试,下面这种赋值方法,也可以
//Box box[3] = { {1, 3, 5}, {2, 4, 6}, {3, 6, 9} };
// 2.下面这种方式,VS报错,测试不了,提示木马。Dev-C++测试成功,可以
//Box box[3] = { Box(1, 3,5), Box(), Box(3, 6, 9) };
// 3.下面这种方式,VS也测试不了,提示木马
//Box box[3] = { {1, 3, 5}, {}, {3, 6, 9} };
return 0;
}
基类的公用成员和保护成员在派生类中保持原有访问属性,
其私有成员仍为基类私有
公用基类成员在派生类中的访问属性
公用基类成员 | 在公用派生类中的访问属性 |
---|---|
公用成员 | 公用 |
私有成员 | 不可访问 |
保护成员 | 保护 |
基类的公用成员和保护成员在派生类中成了私有成员,其私有成员仍为基类所有
私有基类成员在派生类中的访问属性
私有基类的成员 | 在私有派生类中的访问属性 |
---|---|
公用成员 | 私有 |
私有成员 | 不可访问 |
保护成员 | 私有 |
基类的公用成员和保护成员在派生类中成了保护成员,其私有成员仍为基类所有
受保护的意思:不能被外界访问,但可以被派生类的成员访问
基类中的成员 | 在保护派生类中的访问属性 |
---|---|
公用成员 | 保护 |
私有成员 | 保护 |
保护成员 | 不可访问 |
多重继承是指派生类具有两个或两个以上的直接基类(direct class)
多重继承派生类是一种比较复杂的类构造形式
格式
class 派生类名: virtual 继承方式 基类名
例子
class Person {
...
};
// 虚继承于Person类
class Student: virtual public Person {
...
};
// 虚继承与Perosn类
class Employee virtual public Person {
...
};
虚基类并不是在声明基类时声明的,而是在声明派生类时的指定继承方式时声明的
因为一个基类可以在派生一个派生类时,作为虚基类,而在派生另一个派生类时,不作为虚基类
包含子对象派生类构造函数的执行顺序:
1、最先调用基类的构造函数,对基类数据成员初始化。
当派生类有多个基类时,各基类构造函数的调用顺序按照他们在继承方式中的声明次序调用,而不是按派生类构造函数参数初始化列表中出现的次序调用
2、再调用子对象的构造函数,对子对象数据成员初始化。
当派生类有多个子对象时,各子对象构造函数按派生类声明中子对象出现的次序调用,而不是按派生类构造函数参数初始化列表中出现的次序调用
3、最后执行派生类构造函数的函数体,对派生类新增的一般数据成员初始化
包含子对象派生类析构函数的执行顺序与其构造函数的执行顺序相反
1、最先执行派生类析构函数的函数体,对派生类新增的一般数据成员所涉及的额外内存空间进行清理
2、再调用子对象的析构函数,对子对象所涉及的额外内存空间进行清理。
当派生类有多个子对象时,按派生类声明中子对象出现的逆序调用
3、最后调用基类的析构函数,对基类所涉及的额外内存空间进行清理,多个基类则按照派生类声明时列出的逆序、从右到左调用
多态性是指用一个名字定义不同的函数,这些函数执行不同但又类似的操作
从而可以使用相同的调用方式来调用这些具有不同功能的同名函数
C++中多态性分为4类
1、参数多态
2、包含多态
3、重载多态
4、强制多态
前面2种称为通用多态,后面2中称为专用多态
多态从实现的角度分为2类
1、编译时多态
2、运行时多态
绑定工作在编译连接阶段完成的情况称为功能早绑定
绑定工作在程序运行阶段完成的情况称为功能晚绑定
一般而言
编译型语言(C、PASCAL)都采用功能早绑定
解释型语言(LISP、Prolog)都采用功能晚绑定
虚函数的定义是在基类中进行的
在成员函数原型的声明语句之前冠以关键字 virtual ,从而提供一种接口
virtual 函数类型 函数名(参数表) {
函数体
}
虚函数的作用
允许在 派生类 中重新定义 与基类同名的函数,
并且可以通过 指向 基类对象的 指针或 基类对象的 引用来访问 基类和 派生类中的 同名函数
C++规定,如果在派生类中,没有用virtual显示地给出虚函数的声明,这时系统会遵循以下的规则来判断一个成员函数是不是虚函数
派生类的函数满足了上述条件,就被自动确定为虚函数。因此,在本程序的派生类MotorVehicle, Car, Truck 中 message() 仍为虚函数
虚函数的定义说明
1、通过定义虚函数来使用C++提供的多态性机制时,派生类应该从它的基类公用派生。
之所以有这个要求,是因为是在赋值兼容性规则的基础上来使用虚函数的,而赋值兼容规则成立的前提是派生类从其基类公用派生
2、必须首先在基类中定义虚函数。
由于 基类 和 派生类 是相对的,因此,这项说明并不表明必须在类等级的最高层类中声明虚函数。
在实际应用中,应该在类等级内需要应具有动态多态性的几个层次中的最高层类内首先声明虚函数
3、在派生类中对基类声明的虚函数进行重新定义时,关键字 “virtual” 可以写,也可以不写。
但为了增强程序的可读性,最好在对派生类的虚函数进行重新定义时,也加上关键字 “virtual”
如果在 派生类 中没有对基类的 虚函数 重新定义,则派生类简单地继承其 直接基类 的虚函数
4、虽然使用对象名和点运算符的方式也可以调用虚函数,
如语句 “c.message()” 可以调用虚函数 Car::message(),但是这种调用是在编译时进行的功能早绑定,
它没有充分利用虚函数的特性
只有通过指向基类对象的指针或基类对象的引用访问虚函数时,才能获得运行时的多态性
5、一个虚函数无论被公用继承多少次,它仍然保持其虚函数的特性
6、虚函数必须是其所在类的成员函数,而不能是友元函数,也不能是静态成员函数,
因为下滑神女湖调动要靠特定的对象来决定该激活哪个函数。
但是虚函数可以在另一个类中被声明为友元函数
7、內联函数不能是虚函数,因为內联函数是不能在运行中动态确定其位置的。
即使虚函数在类的内部定义,编译时仍将其看做是非內联的
8、构造函数不能是虚函数,因为虚函数作为运行过程中多态的基础,主要是针对对象的,而构造函数是在对象产生之前运行的,
因此徐构造函数是没有意义的
9、析构函数可以是虚函数,而且通常说明为虚函数
在一个派生类中重新定义基类的虚函数不同于一般的函数重载
1、函数重载处理的是同一层次上的同名函数问题,而虚函数处理的是同一类族中不同派生层次上的同名函数问题,
前者是横向重载,后者可以理解为纵向重载。
但与重载不同的是,同一类族的虚函数的首部是相同的,而函数重载时函数的首部是不同的(参数个数或类型不同)
2、重载函数可以是成员函数或普通函数,而虚函数只能是成员函数
3、重载函数的调用是以所传递参数序列的差别作为调用不同函数的依据;
虚函数是根据对象的不同去调用不同类的虚函数
4、虚函数在运行时表现出多态功能,这是C++的精髓;
而重载函数则在编译时表现出多态性
抽象类是带有纯虚函数的类
有时,基类往往表示一种抽象的概念,它并不与具体的事物相联系
纯虚函数是一个在基类中声明的虚函数,它在该基类中没有定义,
但要求在它的派生类中必须定义自己的版本,或重新说明为纯虚函数
纯虚函数的定义
class 类名 {
...
virtual 函数类型 函数名(参数表)=0;
...
}
如果一个类中至少有一个纯虚函数,那么就成该类为抽象类
抽象类使用规定
1、由于抽象类中,至少包含一个没有定义功能的纯虚函数。因此,抽象类只能作为其他类的基类来使用,不能建立抽象类对象,它只能用来为派生类提供一个接口规范,其纯虚函数的实现由派生类给出
2、不允许从具体类派生出抽象类,所谓具体类,就是不包含纯虚函数的普通类
3、抽象类不能用作参数类型、函数返回类型或显式转换的类型
4、可以声明抽象类类型的指针,此指针可以指向它的派生类对象,进而实现动态多态性
5、如果派生类中没有重新定义纯虚函数,则派生类只是简单继承基类的纯虚函数,则这个派生类仍然是一个抽象类。
如果派生类中给出了基类所有纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以创建对象的具体类
6、在抽象类中,也可以定义普通成员函数或虚函数,虽然不能为抽象类声明对象,但仍然可以通过派生类对象来调用这些不是纯虚函数的函数
C++中,还有一种情况是函数体为空的虚函数,请注意它和纯虚函数的区别。
纯虚函数一般没有函数体,而空的虚函数的函数体为空。
前者所在的类是抽象类,不能直接进行实例化,而后者所在的类是可以实例化的。
它们共同的特点是都可以派生出新的类,然后在新类中给出新的虚函数的实现,而且这种新的实现可以具有多态特征
友元函数不是当前类的成员函数,而是独立于当前类的外部函数
但它可以访问该类对象的任何成员,包括私有成员、保护成员和公有成员
说明:
1、如果只声明了类而未定义对象,则类的一般数据成员是不占内存空间的,只有在定义对象时,才为对象的数据成员分配空间。
但是静态数据成员不属于某一个对象,在为对象分配的空间中不包括静态数据成员所占的空间。
静态数据成员是在所有对象之外单独开辟空间。只要在类中定义了静态数据成员,即使不定义对象,也为静态数据成员分配空间,它可以被访问。
在一个类中可以有一个或多个静态数据成员,所有的对象共享这些静态数据成员,都可以访问它
2、静态数据成员不随对象的建立而分配空间,也不随对象的撤销而释放(一般数据成员是在对象建立时分配空间,在对象撤销时释放)。
静态数据成员是在程序编译时,被分配空间的,到程序结束时才释放空间
3、静态数据成员可以初始化,但只能在类体外进行初始化
int Student::stu_count = 0;
不必在初始化语句中加 static
注意:静态数据成员要实际地分配空间,故不能在类声明中初始化。
类声明只声明一个类的“尺寸与规格”,并不进行实际的内存分配,所以在类声明中写"static int stu_count = 0;" 是错误的
如果未对静态数据成员赋初值,则编译系统会自动赋予初值0
4、静态数据成员即可以通过对象名访问,也可以通过类名来访问
这些现在看起来,很多规定,这些理解,是应该理解一下程序编译执行的内存状态来理解的,而不是这样靠记忆
静态成员函数的作用:为了能处理静态数据成员
说明:
1、静态成员函数与非静态成员函数的根本区别是:非静态成员函数有 this指针,而静态成员函数没有this指针,因而决定了静态成员函数不能默认本类中的非静态成员
当调用一个对象的非静态成员函数时,系统会把该对象的起始地址赋给成员函数的 this指针。而静态成员函数并不属于某一对象,它与任何对象都无关,因此静态成员函数没有this指针。
既然它没有指向某一对象,就无法对一个对象中的非静态成员进行默认访问(即在访问数据成员时,不指定对象名)
2、静态成员函数可以直接访问本类中的静态数据成员,因为静态成员同样是属于类的,可以直接访问。
在C++程序中,静态成员函数主要用来访问静态数据成员,而不访问非静态成员。
假如在一个静态成员函数中有以下语句:
cout << age << endl;
cout << score << endl;
但是,并不是绝对不能访问本类中的非静态成员,只是不能进行默认访问,因为无法知道应该去找哪个对象。
如果一定要访问本类的非静态成员,应该加对象名和成员运算符"." .
1、C++不允许用户自己定义新的运算符,只能对C++中已有的运算符进行重载
2、运算符重载针对新类型数据的实际需要,对原有运算符进行适当的改造。一般来讲,重载的功能应当与原有的功能相类似
3、C++允许重载的运算符包括C++中几乎所有的运算符,只有5个运算符不允许重载
4、坚持 “4个不能改变”
不能改变运算符操作数的个数;
不能改变运算符原有的优先级;
不能改变运算符原有的结合性;
不能改变运算符原有的语法结构
5、重载的运算符必须和用户定义的自定义类型对象一起使用,其参数至少应有一个是类对象(或类对象的引用),
也就说,参数不能全部是C++的标准类型,以防止用户修改用于标准类型数据的运算符的性质
6、重载运算符的函数不能有默认的参数,否则就改变了运算符参数的个数
7、用于类对象的运算符一般必须重载,
但又2个例外,"=" 和 “&” 可以不必用户重载
地址运算符 &,也可以不必重载,它能放回类对象在内存中的起始地址
泛型编程 和 面向过程编程以及面向对象编程一起,是C++所包含的三种编程风格
面向过程的编程,可以将常用代码封装在一个函数中,然后通过函数调用来表达目标代码重用的目的
面向对象的编程,可以通过 类的继承 以及 类的聚合或组合 来实现代码的重用。
而泛型编程,是以独立于任何特定类型的方式编写代码,使代码具有更好的重用性
模板是泛型编程的基础,传统C++的泛型编程,仅仅局限于简单的模板技术-函数模板 和 类模板。
随着C++的发展,标准C++引入STL(标准模板库)的内容,才真正进入泛型编程 的广阔天地
C++是一种强类型语言,强类型语言所使用的数据都必须明确地声明为某种严格定义的类型,并且在所有的数值传递中,编译器都强制进行类型相容性检查。
虽然强类型语言有力地保证了语言的安全性和健壮性,但有些时候,强类型语言对于实现相对简单的函数似乎是个障碍
虽然上述程序代码中的Max函数的算法很简单,但是强类型语言迫使我们不得不为所有希望比较的类型都显式定义一个函数,显得既笨拙又效率低下
在模板出现之前,有一种方法可以作为这个问题的一种解决方案,那就是使用带参数宏。
但是这种方法也是很危险的,因为宏的工作只是简单地进行代码文本的替换,它避开了C++的类型检查机制
例8_1中的Max函数可以用下面的宏来替换
#define Max(x, y) x > y ? x : y
实际上,C++编程系统只是在预编译时把程序中每一个出现的Max(x, y)的地方,都使用预先定义好的语句来替换它。
这里就是用 "x > y ? x : y"来替换
该定义对于简单的Max函数调用,都能正常工作,但是在稍微复杂的调用下,它就有可能出现错误。
例如定义了如下的计算平方的带参数宏
#define Square(A) A * A
则如下的调用:
Square(a + 2);
会被替换成 a + 2 * a + 2,实际计算顺序变成了 a + (2 * a) + 2, 而不是我们所期望的 (2 + a) * (2 + a)
另外,宏定义无法声明返回值的类型。
如果宏运算的结果赋值给一个与之类型不匹配的变量,编译器并不能检查出错误
正因为使用宏在功能上的不便和和不进行类型检查的危险,C++引入了模板的概念
所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型中的全部或部分类型不具体制定,用一个虚拟的类型来代表。
这个通用的函数就称为函数模板。
凡是函数体相同的函数,都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可,在函数调用时,系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同函数的功能
template
返回类型 函数名(形参表) {
// 函数体
}
template
返回类型 函数名(形参表) {
// 函数体
}
template 是 定义关键模板 的 关键字,尖括号中先写 关键字 typename(或 class),后面跟一个类型参数 T, 这个类型参数实际上是一个虚拟的类型名,
表示模板中出现的T是一个类型名,但是现在并为指定它是哪一种具体的类型。
在函数定义时,用 T 来定义变量 x, y,显然变量 x, y的类型也是未确定的。
要等到函数调用时根据实参的类型来确定T是什么类型。
参数名T由程序员定义,其实也可以不用T而用任何一个标识符,许多人习惯用T(T是 Type 的第一个字母),而且用大写,以与实际的类型名相区别
class 和 typename 的作用相同,都是表示它后面的参数名代表的一个建在的内置或用户定义的类型,二者可以互换
说明:
1、在定义模板时,不允许 template 语句 与函数模板之间有任何其他语句,下面的模板的定义也是错误的:
template
int a; // 错误,不允许在此位置有任何语句
T Max(T x, T y) {
}
2、不要把这里的class 与 类的声明关键字 “class” 混淆在一起,虽然他们由相同的字母组成,但含义是不同的。
为了区别类与模板参数中的类型关键字 “class”,标准C++提出了用 typename 作为模板参数的类型关键字,同时也支持使用 class,
如果用 typename 其含义就很清楚,肯定是 类型名 而不是 类名
3、函数模板 的 类型参数 可以不止一个,可根据实际需要确定个数,但每个类型参数都必须用关键字 typename 或 class 限定
template
T1 Func(T1 a, T2 b, T3 c) {
}
4、当一个名字被声明为模板参数之后,它就可使用了,一直到模板声明或定义结束为止。
模板类型参数被用作一个 类型指示符,可以出现在模板定义的余下部分。
它的使用方式 与 内置或用户定义的类型完全一样,比如用来声明变量 和 强制类型转换
我们可能对模板中通用函数的表示方法不太习惯,其实对于例8_1来说,在建立函数模板时,只要将例8_1程序中定义的第一个函数首部的 int 改为 T即可,即用虚拟的类型名 T 代替具体的数据类型
当编译器遇到关键字 template 和 跟随其后的函数定义时,它只是简单地知道,这个函数模板在后面的程序代码中可能会用到。
除此之外,编译器不会做额外的工作。
在这个阶段,函数模板本身并不能使编译器产生任何代码,因为编译器此时并不知道函数模板要处理的具体数据类型,根本无法生成任何函数代码
当编译器遇到程序中对函数模板的调用时,它才会根据调用语句中实参的具体类型,确定模板参数的数据类型,并用此类型替换模板函数中的模板参数,生成能够处理该类型的函数代码,即模板函数
例如,在例8_2的程序中,当编译器遇到:
template
T Max(T x, T y) {
}
时,并不会产生任何代码,但当遇到函数调用Max(a, b), 编译器会将函数名 Max 与 模板 Max 相匹配,
将实参的类型取代函数模板中的 虚拟类型 T, 生成下面的模板函数
int Max(int x, int y) {
return x > y ? x : y;
}
然后调用它。编译器对例8_2程序中其他两个函数调用Max(a, b)、Max(e, f)的处理与此类似。
从这里我们可以看出:表面上是在调用 模板,实际上是调用 其实例
那么,是否每次调用函数模板时,编译器都会生成相应的模板函数呢?例如在例8_2中有下面的函数调用:
int u = Max(1, 2);
int v = Max(3, 4);
int w = Max(5, 6);
编译器是否会实例化生成 3 个相同的Max(int, int)模板函数呢?答案是否定的。
编译器只在第1次调用时生成模板函数,当之后遇到 相同类型的参数调用时,不再生成其他模板函数,它将调用第1次实例化生成的模板函数。
可以看出,用 函数模板 比用 函数重载 更方便,程序更 简洁。
但它只适用于函数的 参数 个数相同而 类型不同,且 函数体相同 的情况,如果 参数的个数 不同,则不能用 函数模板
C++在 实例化 函数模板的过程中,只是简单地将 模板参数 替换成 调用 实参的类型,并以此生成 模板函数,不会进行 参数类型的 任何转换。
这种方式与 普通函数 的参数处理有着 极大的区别,在普通函数的调用过程中,C++会对 类型不匹配 的参数进行 隐式的 类型转换。
例如,在例8_2的main函数中再添加如下语句:
cout << "2, 2.3两数中的大者为:" << Max(2, 2.3) << endl;
cout << " 'a',2两数中的大者为: " << Max('a', 2) << endl;
这2个错误的编号都是C2782,它们都是同一类型的错误,即 模板参数 不匹配。
产生这种类型错误的原因就是在 模板实例化过程中,C++不会进行任何形式的 参数类型 转换,
当编译器遇到下面的调用语句时
cout << "2, 2.3两数中的大者为: " << Max(2, 2.3) << endl;
它首先调用 实参 的类型 实例化函数模板 T Max(T x, T y),生成模板函数。
由于 Max(2, 2.3)的调用实参类型分别是 int 和 double,而 Max函数模板中只有一个类型参数 T,因此,这个调用与模板声明不匹配,
于是产生上述编译错误.这类问题的解决方法有以下几种。
1、在 模板调用 时进行参数类型的 强制转换,如下所示:
cout << "2, 2.3两数中的大者为:" << Max(double(2), 2.3) << endl;
cout << " 'a', 2两数中的大者为: " << Max(int('a'),2) << endl;
2、通过提供 "<>"里面的 参数类型 来调用 这个模板,如下所示:
cout << "2, 2.3两数中的大者为:" << Max(2, 2.3) << endl;
cout << " 'a', 2两数中的大者为: " << Max('a', 2) << endl;
Max(2, 2.3)告诉编译器用 double实例化 函数模板 T(T x, T y),之后 第1个实参 由系统自动通过 标准类型 转换规则转型成 double 型数据
Max(‘a’, 2)告诉编译器用 int 实例化函数模板T Max(T x, T y),之后第1个实参由系统自动通过 标准类型 转换规则 转型成 int型数据
当有多个不同的模板参数时,就要在函数调用名后面的"<>"中分别提供各个模板参数的类型
3、指定多个模板参数
对于 例8_2的 Max函数模板参数由 一个 改为 两个
编译该程序,将不再会产生编译错误。但函数的运行结果并不精确,甚至存在较大的误差,但它并不表示程序有什么错误。
其原因是:Max函数模板的返回值类型 依赖于 模板参数T1。
如果在调用时,将精度高的数据类型作为第1个参数,结果将是正确的。
上述语句如果改写成如下形式:
cout << "2, 2.3两数中的大者为:" << Max(2.3, 2) << endl;
cout << " 'a', 2两数中的大者为: " << Max(2, 'a') << endl;
将得到正确的运行结果
函数模板 形参表中除了可以出现用 typename 或 class 关键字声明的类型参数外,还可以出现确定类型参数,称为非类型参数。
如
template
T1 Func(T1 a, T2 b, T3 c) {
}
上述函数模板形参表中的 T1, T2, T3是类型参数,T4是非类型参数。
类型参数代表任意 数据类型,在模板调用时 需要用实际类型来替代。
而非类型参数是指 某种具体的 数据类型,由一个普通的参数 声明构成
模板非类型参数名 代表了 一个 潜在的值。它被用作一个常量值,可以出现在模板定义的余下部分。
1、它可以用在要求常量的地方
2、或是在数组声明中指定数组的大小或 作为 枚举常量的 初始值。
在模板调用时 只能为其提供相应类型的常数值。
非类型参数 是 受限制的,通常可以是 整型、枚举型、对象 或 函数的引用,以及对象、函数 或 类成员的 指针,但不允许用 浮点型(或双精度型)、类对象或 void作为 非类型参数
例8_4中,size被声明为 BubbleSort 函数模板的 非类型参数后,在模板定义的余下部分,它被当做一个常量值使用。
1、由于BubbleSort函数不是通过引用来传递数组,故在模板调用时,必须显式指定模板实参,明确指定数组的大小。
2、如果BubbleSort函数是 通过引用来传递数组,则在模板调用时,就可以不显示指定 模板实参,而由编译器自动推断出来,如下所示:
template
// 声明 a 为一维数组的引用
void BubbleSort(T (&a)[size]) {
}
main函数中的两个函数调用语句改为
BubbleSort(a);
BubbleSort(b);
不过,该程序用VC++6.0的C++编译器不能编译通过,可改用其他编译器,如:
VC++7.0的C++编译器、GCC编译器
另外,本程序也可以不使用模板 非类型参数 来指定 数组的大小,而在 BubbleSort函数的参数表中 增加 一个传递数组大小的 Int型参数。
修改后的函数模板代码如下:
template
// 增加的形参n,用于传递数组的大小
void BubbleSort(T a[], int n) {
for (i = n - 1, change = true; i >= 1 && change; --i) {
//此处代码与例8_4中的相同,故省略不写
}
}
同时,修改main函数中的两个函数调用语句为:
BubbleSort(a, 10);
BubbleSort(b, 10);
综上所述:
函数模板形参表中可以出现用 typename 或 class 关键字声明的类型参数,还可以出现由 普通参数声明构成的非类型参数。
除此之外,函数模板形参表中, 还可以出现类模板类型的参数,在模板调用时,需要为其提供一个类模板。
关于这部分内容会在本章 8.2.3节中 详细介绍
像普通函数一样,也可以用相同的函数名重载 函数模板。
实际上,例8_2中的Max函数模板并不能 完成 两个字符串数据的大小比较,
如果在 例8_2中的main函数中添加 如下语句:
char* s1 = "Beijing 2008", *s2 = "Welcome to Beijing";
cout << "Beijing 2008,Welcome to Beijing两个字符串中的大者为:" << Max(s1, s2) << endl;
运行程序,可以返现上述输出语句的执行结果为:
Beijing 2008, Welcome to Beijing两个字符串中的大者为:Beijing 2008
结果是错误的。
其原因是:函数调用Max(s1, s2)的实参类型 为 char*, 编译器用 char* 实例化 函数模板 T Max(T x, T y), 生成下面的 模板函数:
char* Max(char* x, char* y) {
return x > y ? x : y;
}
这里实际比较的不是两个字符串,而是两个字符串的地址。
哪一个字符串的存储地址高,就输出哪个字符串。
从输出结果看,应该是 "Beijing 2008"的地址高。为了验证这一点,用语句:
cout << &s1 << "" << &s2 << endl;
输出 s1 和 s2 的地址,结果为:
0012FF7C 0012FF78
果真是 Beijing 2008的存储地址高。
处理这种异常情况的方法可以有如下两种。
1、对函数模板进行重载,增加一个与函数模板同名的 普通函数 定义
#include
char* Max(char* x, char* y) {
cout << "This is the overload function with char*, char* !max is: ";
reutrn strcmp(x, y) > 0 ? x : y;
}
2、改变函数调用 Max(s1, s2)的实参类型为 string, 这样编译器就用 string 实例化函数模板 T Max(T x, T y),生成下面的模板函数
#include
string Max(string x, string y) {
return x > y ? x : y;
}
而两个 string 类型 的数据是可以用 “<” 或 “>” 等运算符比较大小的。
这里好像是第1次,在C++中出现使用 string的情况,之前都是使用 char*
string,好像是 C++11增加的
体会一下
运用 函数模板 可以设计出与 具体数据类型 无关的 通用函数,与此类似,C++也支持用 类模板 来设计 结构和 成员函数完全相同,
但所处理的 数据类型 不同的 通用类,在设计 类模板时,可以使其中的某些 数据成员、成员函数 的 参数 或 返回值 与 具体类型 无关
模板在C++中更多的使用是在类的定义中,最常见的就是STL(Standard Template Library)和 ATL(ActiveX Template Library),它们都是作为
C++标准集成在VC++开发环境汇总的标准模板库
template
class 类名 {
// 类体
};
与函数模板一样,template是定义类模板的关键字,"<>"中的 T1, T2是 类模板的类型参数。
在一个类模板中,可以定义多个不同的类型参数。"<>"中的 typename 可以用 class 代替,它们都表示 其后的参数名代表 任意类型,但与 "类名"前的 class
具有不同的含义,二者没有关系。“类名” 前的 class 表示类的声明
下面以 Stack 类模板的定义为例,说明类模板的 定义方法
说明:
1、类模板中的成员函数既可以在类模板内定义,也可以在类模板外定义
如果在类模板内定义成员函数,其定义方法与普通类成员函数的定义方法相同,
如Stack的构造函数、判断栈是否为空的StackEmpty 函数、判断栈是否已满的StackFull函数的定义。
如果在类模板外定义成员函数,必须采用如下形式:
template<模板参数列表>
返回值类型 类名<模板参数名表>::成员函数名(参数列表) {
};
例如,例8_8中的Stack的 Push成员函数的定义:
// 类模板声明
template
void Stack::Push(T e) {
}
注意:在引用模板的类名的地方,必须具有该模板的参数名表
2、如果要在类模板外将成员函数定义为 inline函数,应该将 inline关键字加在 类模板的声明后。
例如,例8_8中的Stack 的 Pop成员函数的定义:
// 类模板声明
template
// 指定为內联函数
inline T Stack::Pop() {
}
3、类模板的成员函数的定义必须与类模板的定义在同一个文件中。
因为,类模板的定义不同于类的定义,编译器无法通过一般的手段找到类模板成员函数的代码,只有将它和类模板定义放在一起,才能保证类模板正常使用。
一般都放入一个.h头文件中
在声明了一个类模板后,怎么使用它?请看下面这条语句
Stackint_stack;
该语句用 Stack类模板定义了一个对象int_stack.
编译器遇到该语句,会用int去替换 Stack类模板中的所有类型参数T, 生成一个针对int型数据的具体的类,一般称为模板类,该类的代码如下:
class Stack {
private:
int data[10];
// 栈顶指针
int top;
public:
Stack() {
top = 0;
}
// 入栈操作
void Push(int e);
// 出栈操作
int Pop();
// 判断栈是否为空
bool StackEmpty() {
return top == 0;
}
// 判断栈是否已满
bool StackFull() {
return top == 10;
}
}
最后,C++用这个模板类定义了一个对象 int_stack.
从图8_2中可以看出,类模板、模板类及 模板对象 之间的关系为:由类模板实例化 生成 针对具体数据类型的 模板类,再由模板类 定义 模板对象
用模板类 定义 对象的形式如下:
类模板名<实际类型名表>对象名;
类模板名<实际类型名表>对象名(构造函数实参表);
由类模板创建其 实例模板类 时,必须为 类模板的 每个 模板参数显式指定模板实参,然而由 函数模板 创建 其实例模板函数时,可以不显式指定模板实参,这时编译器会自动根据函数 调用时 的 实参来推断出。
注意,在类模板实例化的过程中,并不会实例化 类模板的成员函数,也就是说,在用类模板定义对象时,并不会生成类成员 函数的代码。
类模板成员函数的 实例化发生在 该成员函数被调用时,这就意味着只有那些 被调用的成员函数才会被实例化。
或者说,之后当成员函数被调用时,编译器才会为它生成 真正的函数代码。
例如,对于例8_8的Stack类模板,假设有下面的 main函数:
int main() {
Stackint_stack;
for (int i = 1; i < 10; i++) {
int_stack.Push(i);
}
return 0;
}
在上述main函数中,并没有调用 Stack 的 Pop、StackEmpty、StackFull成员函数,所以C++编译器在 Stackint_stack的实例化过程中,
不会生成 Pop, StackEmpty, StackFull的函数代码。
作为验证,可以将例8_8的Stack类模板中的 Pop成员函数的类外定义删掉,
同时将Stack 中的 StackEmpty和 StackFull这两个函数的定义修改为如下的声明,然后再编译运行该程序,可以返现程序同样可以正确执行
template
class Stack {
private:
T data[SSize];
int top;
public:
Stack() {
top = 0;
}
void Push(T e);
T Pop();
bool StackEmpty();
bool StackFull();
}
template
void Stack::Push(T e) {
if(top == SSize) {
cout << "Stack is Full! Don't push data!" << endl;
return;
}
data[top++] = e;
}
由于类模板 包含 类型参数,因此又称为 参数化的 类。
如果说 类 是 对象的抽象,对象是 类的实例,则 类模板 是 类的抽象,模板类 是 类模板 的 实例。
利用 类模板 可以 建立 各种数据类型 的 类。
下面的程序代码 展示了 Stack类模板的使用方法,在该程序的main函数中 实现了一个整数栈 和 一个 字符栈
#include "Stack.h"
#include
using namespace std;
int main() {
Stackint_stack;
Stackchar_stack;
for (int i = 1; i < 10; i++) {
int_stack.Push(i);
}
}
与 函数模板 的 模板参数 一样,类模板 的 模板参数 也可以出现 非类型参数。
对于例8_8的堆栈类 模板 Stack, 也可以不定义一个 int型常变量 SSize 来 指定栈的容量大小,而改成为其增加 一个 非类型参数
修改后的堆栈类模板 Stack 的定义如下:
// stack.h
template
class stack {
private:
T data[SSize];
int top;
public:
Stack() {
top = 0;
}
void Push(T e);
T Pop();
bool StackEmpty() {
return top == 0;
}
bool StackFull() {
return top == SSize;
}
}
template
void Stack::Push(T e) {
if (top == SSize) {
cout << "Stack is Full! Don't Push data! " << endl;
return;
}
}
在类模板中,可以为 模板参数 提供 默认值,但是在 函数模板中却不行。
例如,为了使 上述固定大小的 Stack类模板 更友好一些,可以为其 非类型模板参数 SSize 提供 默认值,如下所示:
template
class Stack {
private:
T data[SSize];
int top;
public:
...
}
类模板参数 的 默认值 是一个 类型 或 值。
当 类模板被 实例化时,如果没有指定实参,则使用 该类型或值。注意:默认值应该是一个 "对类模板实例的多数情况都适合"的 类型 或 值。
现在,如果在声明一个 Stack模板对象时, 省略了 第2个模板实参,SSize的值将去默认值10
说明:
1、作为默认 的 模板参数,它们只能被定义 一次,编译器会知道 第1次 的模板舍命或定义
2、指定默认值的 模板参数 必须放在模板实参表 的 右端,否则出错
3、可以为所有 模板参数 提供默认值,但在声明 一个实例时,必须使用一对空的 尖括号,这样编译器就知道 说明了 一个 类模板
类模板 的 模板形参表 中 的 参数类型有 3中:类型参数、非类型参数、类模板类型的参数,
函数模板的模板参数类型也 于此相同。
对于前2种类型的模板参数,我们已经比较熟悉了。
下面看一个 类模板类型的模板参数 的例子
Container 类模板 有 3个参数:类型参数T,非类型参数size, 类模板类型 的 模板参数 Seq.
Seq 又有 2个模板参数:类型参数T,非类型参数size.
在 main函数中 使用了 一个持有 整数的 Array 将 Container 实例化,因此,本例中的 Seq 代表 Array
注意:在例8_9Container的声明中,对Seq的参数进行命名,不是必需的,可以这样写:
templateclass Seq>
无论什么地方参数U、S都不是必需的
特别提醒:
由于VC++6.0 不支持 模板嵌套,例8_9在 VC++6.0下,编译通不过,可以选择高版本的 VS2008, VS2010, VS2013等
STL 是 Standard Template Library(标准模板库)的缩写,是一个高效的C++程序库,
它被容纳于C++标准程序库(C++ Standard Library)中,是 ANSI/ISO C++标准的一部分。
该库包含了诸多在计算机科学领域里所常用的基本数据结构和基本算法。
为广大C++程序员们提供了一个可扩展的应用框架,高度体现了软件的可重用性。
STL的代码从广义上讲分为三类:容器(container)、迭代器(iterator)和算法(algorithm),几乎所有的代码都采用了模板(类模板和函数模板)的方式,这相比于传统的由函数和类组成的库来说,提供了更好的代码重用的机会。
在C++标准中,STL被组织为13个头文件:algorithm.h, deque.h, functiona.h, iterator.h, vector.h, list.h, map.h, memory.h, numeric.h, queue.h, set.h, stack.h, 和 utility.h
从实现的角度看,STL容器是一种 类模板。
STL是经过精心设计的,为了减少使用容器的难度,大多数容器都提供了相同的成员函数,尽管一些成员函数的实现是不同的。
下面先通过表8-1~表8-3来了解一下STL中的常用容器及其公用的成员函数。
容器名 | 头文件名 | 说明 |
---|---|---|
vector | vector.h | 向量,从后面快速插入和删除,直接访问任何元素 |
list | list.h | 双向链表 |
deque | deque.h | 双端队列 |
set | set.h | 元素不重复的集合 |
multiset | set.h | 元素可重复的集合 |
stack | stack.h | 堆栈,后进先出(LIFO) |
map | map.h | 一个键只对应一个值的映射 |
multimap | map.h | 一个键可对应多个值的映射 |
queue | queue.h | 队列,先进先出(FIFO) |
priority_queue | queue.h | 优先级队列 |
表8-2 所有容器都具有的成员函数
成员函数名 | 说明 |
---|---|
默认构造函数 | 对容器进行默认初始化的构造函数(容器的构造函数常有多个,用于提供不同的容器初始化方法) |
复制构造函数 | 用于将容器初始化为同类型的现有容器的副本 |
析构函数 | 执行容器销毁时的清理工作 |
empty() | 判断容器是否为空,若未空返回true, 否则返回false |
max_size() | 返回容器最大容量,即容器能够保存的最多元素个数 |
size() | 返回容器中当前元素的个数 |
swap() | 交换两个容器中的元素 |
除了 priority_queue容器之外,其他容器还有重载的赋值和关系操作符函数,如表8-3所示
表8-3 除 priority_queue 容器外其他容器还有的 关系操作符 重载函数
成员函数名 | 说明 |
---|---|
operator= | 将一个容器赋给另一个同类容器 |
operator< | 如果第1个容器小于第2个容器,则返回true, 则返回 true, 否则返回 false |
operator<= | 如果第1个容器小于等于第2个容器,则返回 true, 否则返回 false |
operator> | 如果第1个容器大于第2个容器,则返回 true, 否则返回 false |
operator>= | 如果第1个容器大于等于第2个容器,则返回 true, 否则返回 false |
并且,除了stack, queue 和 priority_queue 之外,其他容器中均有的 通用成员函数,
还包括 begin, end, rebegin, rend, erase 以及 clear
容器 是容纳、包含一组元素 或 元素集合 的对象。
不管是C++内建的基本数据类型还是用户 自定义的类类型的数据,都可以存入 STL的容器中。
注意:
如果存储在容器中的元素的类型是用户自定义的类类型,那么该自定义类中必须提供重载一些关系操作符的函数(至少需要重载"==“和”<",还可能需要重载"!=“和”>")
即使程序并不需要用到它们。
另外,如果用户自定义的类中有指针数据类型成员,则该自定义类还必须提供复制构造函数 和 赋值运算符重载函数 operator=,
因为插入操作使用的是 插入元素的 一个副本,而不是元素本身
STL容器按存取顺序大致分为两种:
序列容器 和 关联容器。
序列容器 主要包括 vector(向量)、list(表)、deque(双端队列)、stack(堆栈)、queue(队列)、priority_queue(优先队列)等。
其中,stack和queue由于只是将deque 改头换面,其实是一种 容器适配器,但它的用途在软件领域比 deque广泛。
priority_queue也是一种 容器适配器,序列容器 只能包含一种类型的数据元素,而且各元素的排列顺序完全按照元素插入时的顺序。
关联容器主要包括 set(集合)、multiset(多重集合)、map(映射)、multimap(多重映射),可以存储值的 集合 或 键值对。
键 是 关联容器中 存储在 有序序对中的 特定类型的值。
set 和 multiset 存储和操作的只是键,其元素是由 单个数据构成。
map 和 multimap 存储和操作的是键 和 与 键相关的值,其元素 是有关联的 "<键,值>"数据对。
下面来学习几种常见的容器。
vector(向量)类似于数组,它存储具有相同数据类型的一组元素,可以从后面快速地插入与删除元素,可以快速递随机访问元素,但是在序列中间插入、删除元素较慢,
因为需要移动插入或删除处后面的所有元素。
向量能够动态改变自身大小,当要将一个元素插入到一个已满的向量时,会为向量分配一个更大的内存空间,将向量中的元素复制到新的内存空间中,然后释放旧的 内存空间。
但是重新分配更大空间需要进行大量的元素复制,从而增大了性能开销。
图8-3 是 向量的一个示意图。
表8-4列出了它的主要成员函数
在图8-3中,v是一个整型变量。begin、end是向量的头尾查找函数,rbegin, rend是反向查找向量头尾的函数。
iterator 代表指向某个元素的迭代器,通过它可以遍历向量。
成员函数名称 | 功能 |
---|---|
void push_back(const T& el) | 在向量的尾部添加一个元素el |
void pop_back() | 删除向量的最后一个元素 |
iterator insert(iterator i, const T& el = T()) | 在迭代器i引用的元素之前插入el, 并返回新插入元素的迭代器 |
void insert(iterator i, size_type n, const T& el) | 在迭代器i引用的元素之前插入el的n个副本 |
void insert(iterator i, iterator first, iterator last) | 在迭代器i引用的元素之前插入迭代器first 和 last知识的范围中的元素 |
iterator erase(iterator i) | 删除迭代器i引用的元素,返回一个迭代器,它引用被删除元素之后的元素 |
iterator erase(iterator first, iterator last) | 删除迭代器first 和 last 指示的范围中的元素 返回一个迭代器,它引用被删除的最后一个元素之后的元素 |
bool empty() const | 判断向量是否为空,若空,返回true, 否则返回 true |
T& front() | 返回向量的第一个元素 |
T& back() | 返回向量的最后一个元素 |
void clear() | 删除向量中的所有元素 |
iterator begin() | 返回一个迭代器,该迭代器引用向量的第一个元素 |
iterator end() | 返回一个迭代器,该迭代器位于向量的最后一个元素之后 |
reverse_iterator rbegin() | 返回位于向量中最后一个元素的迭代器 |
reverse_iterator rend() | 返回位于向量中第一个元素的迭代器 |
vector() | 构造空向量 |
vector(size_type n, const T& el = T()) | 用类型T的n个副本构造一个向量 (如果没有提供el, 则使用默认的构造函数T()) |
vector(iterator first, iterator last) | 用迭代器first和last指示的范围中的元素构造一个向量 |
vector(const vector& v) | 复制构造函数 |
为了使用vector类,在程序开头必须包含如下的include命令:
#include
vector
v.push_back(object)函数表示把属于该类型的数据存入 vector 容器的尾部
特别提醒:
STL中的所有组件都是在std名字空间中 声明和定义的,所以必须在程序包含头文件命令 “#include"语句的下面加入对std名字空间的应用的语句"using namespace std;”
STL中的list(表)是一个双向链表,可以从头到尾或从尾到头访问链表中的结点,结点可以是任意数据类型。
链表中结点的访问常常通过迭代器进行。
它的每个元素间用指针相连,不能快速访问元素。
为了访问表容器中指定的元素,必须从第一个位置(表头)开始,随着指针从一个元素到下一个元素,知道找到要找的元素。但插入元素比vector块,而且对每个元素分别分配空间,不存在空间不够、重新分配的情况。
图8-4是list的一个示意图。
表8-5列出了它的主要成员函数。
表8-5 list的主要成员函数
成员函数 | 功能 |
---|---|
void push_front(const T& el) | 在链表头插入 el |
void push_back(const T& el) | 在链表尾插入 el |
void pop_front() | 删除链表的第一个结点 |
void pop_back() | 删除链表的最后一个结点 |
void remove(const T& el) | 删除链表中包含 el 的全部结点 |
iterator insert(iterator i, const T& el = T()) | 在迭代器i引用的结点之前插入el,并返回引用新结点的迭代器 |
void insert(iterator i, size_type n, const T& el) | 在迭代器i引用的结点之前插入el的n个副本 |
void insert(iterator i, iterator first, iterator last) | 在迭代器i引用的结点之前,插入从 first 所引用的位置到 last 所引用的位置中的元素 |
由于list的 erase, clear, end, begin, rend, rbegin等成员函数的功能与 vector中相同的成员函数 的 功能相同,故不再在表8-5中一一列出
1、cout流对象
需要注意的是:
1、系统已经对 "<<"运算符作了重载函数,因此用 “cout<<” 输出基本类型的数据时,可以不必考虑数据时什么类型
2、在 iostream.h头文件中只对 "<<“和”>>"运算符用于标准类型数据的输入输出进行了重载,但未对用户自定义类型数据的输入输出进行重载
3、在用 cout 进行输出时,每输出一项都要用一个 "<<"运算符,例如:输出语句
cout << "a = " << a << "b = " << b << endl;
不能写成
cout << "a = a," ", "b = ", b" << endl;
2、ceer流对象
cerr流对象是标准出错流,它的作用是向输出设备输出出错信息。
cerr与标准输出流cout的作用和用法类似。
但不同的是:
cout流通常是传送到显示器输出,但也可以被重定向输出到磁盘文件,而cerr流中的信息只能在显示器输出。
当调试程序时,如果不希望程序运行时的出错信息被送到其他文件,这是应该用cerr
3、clog流对象
clog流对象也是标准出错流,它的作用和cerr相同,都是在终端显示器上显示出错信息。
只不过cerr是不经过缓冲区,直接箱显示器上输出有关信息,而clog中的信息存放在缓冲区中,缓冲区满后或遇endl时才向显示器输出
ostream类的成员函数 put 提供了一种将单个字符送进输出流的方法,其使用方法如下:
char a = 'm';
cout.put(a); // 会输出显示字符a
cout.put('m'); // 会输出和上一句相同的结果
另外,调用put函数的实参还可以是字符的ASCII码或者是一个整型表达式,如:
cout.put(65 + 32); // 显示字符a,因为97是字符a的ASCII码
可以在一个语句中连续调用put函数,如:
cout.put(71).put(79).put(68).put('\n'); // 在屏幕上显示 GOOD
还可以用 putchar 函数输出一个字符。
putchar 函数是C语言中使用的,在stdio.h头文件中定义。
C++保留了这个函数,在iostream.h头文件中定义
ostream类的成员函数 write 是一种将字符串送到输出流的方法,
该函数在 iostream类体中的原型声明语句如下:
ostream& write(const char* pch, int nCount);
ostream& write(const unsigned char* puch, int nCount);
ostream& write(const signed char* psch, int nCount);
其中,第1个参数是待输出的字符串,第2个参数是输出字符串的字符个数,
如输出字符串常量 “C++ program”,可以这样实现:
cout.write("C++program", strlen("C++program"));
标准输入流 是从标准输入设备(键盘)流向内存的数据
istream类的成员函数get可以从输入流中获取一个字符,该函数在 iostream类体中的原型声明语句如下:
int get(); // 从输入流中获取单个字符或EOF,并返回它
istream& get(char& rch); // 从输入流中获取单个字符
istream& get(unsigned char& ruch);
istream& get(signed char& rsch);
istream& get(char* pch, int nCount, char delim = '\n');
istream& get(unsigned char* puch, int nCount, char delim = '\n');
istream& get(signed char* psch, ing nCount, char delim = '\n');
istream& get(streambuf& rsb, char delim = '\n');
1、无参的get函数
无参数的get函数的作用是从指定的输入流中提取一个字符,函数的返回值就是读取的字符。
若遇到输入流中的文件结束符,则函数值返回文件结束标志EOF,看下面的例子
#include
using namespace std;
int main() {
char c;
cout << "Enter a sentence:" << endl;
while( (c = cin.get() != EOF) ) {
cout.put(c);
}
cout << "OK!" << endl;
return 0;
}
C语言中的getchar函数与istream类的成员函数get的功能相同,在C++中依旧可以使用
2、有一个参数的get函数
以 "istream& get(char& rch);"为例介绍,其调用形式为
cin.get(c
其作用是从输入流中读取一个字符,赋给字符变量c。
如果读取成功则函数返回非0值(真),如失败(遇文件结束符)则函数返回0值(假)。
istream类的成员函数getline的作用是从输入流中读取一行字符,
该函数在iostream类体中的原型声明语句如下:
istream& getline(char* pch, int nCount, char delim = '\n');
istream& getline(unsigned char* puch, int nCount, char delim = '\n');
istream& getline(signed char* psch, ing nCount, char delim = '\n');
该函数的性餐标和用法和上面讲述的利用get函数输入一行字符的功能类似。
istream类的成员函数read可以从输入流中读取指定数目的字符并将它们存放在指定的数组中,该函数在iostream类体中的原型声明语句如下:
istream& read(char* pch, int nCount);
istream& read(unsigned char* puch, int nCount);
istream& read(signed char* psch, int nCount);
其中, pch是用来存放读取来的字符的字符指针或字符数组,nCount是一个int型数,用来指定从输入流读取字符的个数
除了以上介绍的用于读取数据的成员函数外,istream类还有其他在输入数据时用得着的一些成员函数。
常用的有以下几个.
1、eof函数
eof是 end of file的缩写,表示 “文件结束”。
从输入流读取数据,如果到达文件末尾(遇文件结束符),则函数值为非零值(表示真),否则为0(假)
其调用格式为:
cin.eof();
2、peek函数
peek函数的作用是从输入流中返回下一个字符,但它只是观测,指针仍停留在当前位置,遇到流结束标志时返回EOF,其调用形式为
c = cin.peek();
3、putback函数,putback函数的调用形式为:
cin.putback(ch);
其作用是将前面用get或getline函数从输入流中读取的字符 ch 返回到输入流,插入到当前指针位置,以供后面读取
输入运算符 ">>"也称为 流提取运算符,是一个二目运算符,有两个操作数;
左操作数 是 istream类的一个对象,
右操作数既可以是一个预定义的变量,也可以是重载了该运算符的类对象
因此,输入运算符不仅能够识别预定义类型的变量,如果某个类中重载了这个运算符,它也能识别这个类的对象
在使用输入运算符时需要注意一下几点。
1、在默认情况下,运算符 ">>"跳过空白符,然后读取与输入变量类型相对应的值。
因此,给一组变量输入值时,可以用空格或换行符把输入的数值间隔开
2、当输入字符串时,运算符 ">>"会跳过空白符,因此读取的字符串中不要有空格,否则认为是结束。
3、不同类型的变量一起输入时,系统出了检查是否有空白符外,还完成输入数据域变量类型的匹配,如:
int n;
float x;
cin >> n >> x;
如果输入:
33.33 22.22
4、输入运算符采用左结合方式,可以将多个输入操作结合到一个语句中
输出运算符 “<<” 也称为流插入运算符,是一个二目运算符,有两个操作数:
左操作数是 ostream类的一个对象,
右操作数既可以是一个预定义的变量,也可以是重载了该运算符的类对象
因此,输入运算符不仅能够识别预定义类型的变量,如果某个类中创在了这个运算符,它也能识别这个类的对象
在使用输出运算符时需要注意以下几点。
1、输出运算符也采用左结合方式,可以将多个输入操作结合到一个语句中,如
int n = 1;
double m = 1.1;
cout << n << "," << m << endl;
2、使用输出运算符时,不同类型的数据也可以组合在一条语句中,编译程序会根据在 "<< "右边的变量或常量的类型,决定调用重载该运算符的哪个重载函数
C++的I/O流类库的一个重要特征是能够支持新的数据类型的输入输出,用户可以通过输入运算符 “>>” 和输出运算符 “<<” 进行重载来支持新的数据类型的输入输出
在输出数据时,为简单起见,往往不指定输出的格式,而由系统根据输出数据的类型采取默认的格式,
但有时我们希望数据按指定的格式输出,
有两种方法可以达到此目的
一种是 使用流对象的有关成员函数
另一种是 使用控制符
在ios类中声明了数据成员x_flags(声明语句:long x_flags),它存储控制输入输出格式的状态标志,每个状态标志栈一位。
状态标志的值只能是ios类中定义的枚举量
标志位 | 值 | 含义 | 输入输出 |
---|---|---|---|
skipws | 0x0001 | 跳过输入流中的空白符 | i |
left | 0x0002 | 输出数据按输出域左对齐 | o |
right | |||
如果设定了某个状态标志,则x_flags中对应位为1,否则为0。这些状态标志之间是或的关系,可以几个标志并存
ios类中定义了数据成员 x_flags来记录当前格式化的状态,即各标志位的设置值,这个数据成员被称为标志字。
格式标志在类ios中被定义为枚举值。
在ios类中,除了提供了操作状态标志的成员函数外,还提供了设置域宽、填充字符和对浮点数设置精度的成员函数来对输出进行格式化
1、设置域宽的成员函数width, 该成员函数有以下两种形式
int width()
该函数用来返回当前输出数据时的宽度
int width(int wid)
该函数用来设置当前输出数据的宽度为参数值wid,并返回更新前的宽度值
注意:
如果输出宽度没有设置,那么默认情况下为数据所占的最少字符数。
所设置的域宽仅对下一个输出流有效,当一次输出完成后,域宽恢复为0
2、设置填充字符的成员函数fill, 该成员函数有以下两种形式:
char fill()
该函数用来返回当前所使用的的填充字符
char fill(char c)
带参数的fill函数用来设置填充字符为参数c字符,并返回更新前的填充字符
注意:
如果填充字符省略,那么默认填充字符为 空格符。
如果所设置的数据宽度小于数据所需的最少字符数,则数据宽度按默认宽度处理
3、设置浮点数输出精度的成员函数precision,该成员函数有以下两种形式
int precision()
该函数返回当前浮点数的有效数字的个数
int precision(int n)
该函数设置浮点数输出时的有效数字个数,并返回更新前的值
注意:
float型实数最多提供7位有效数字,double型实数最多提供15位有效数字,long double型实数最多提供19位有效数字
使用ios类的成员函数来控制输入或输出格式时,必须由流对象来进行调用,使用不方便。
我们可以使用特殊的、类似于函数的控制符来进行控制。
控制符可以直接嵌入到输入或输出操作的语句中。
C++提供的控制符如表9-3所示
控制符名称 | 含义 | 输入输出 |
---|---|---|
dec | 数据采用十进制表示 | |
hex | 数据采用十六进制表示 | |
oct | 数据采用八进制表示 | |
setbase(int n) | 设置数据格式为n(取值0,8,10或16),默认值为0 | |
showbase/noshowbase | 显示/不显示数值的基数前缀 | |
showpoint/noshowpoint | 显示/不显示小数点(只有当小数部分存在时才显示小数点) | |
showpos/noshowpos | 在非负数中显示/不显示+ | |
skipws/noskips | 输入数据时,跳过/不跳过空白符 | |
upercase/nouppercase | 十六进制显示为0X/0x,科学计数法显示E/e | |
ws | 跳过开始的空白字符 | |
ends | 插入空白字符,然后刷新ostream缓冲区 | |
endl | 插入换行字符,然后刷新ostream缓冲区 | |
flush | 刷新与流相关联的缓冲区 | |
resetiosflags(long f) | 清除参数所指定的标志位 | |
setiosflags(long f) | 设置参数所指定的标志位 | |
setfill(char c) | 设置填充字符 | |
setw(int n) | 设置域宽 | |
这些控制符是在iomanip.h中定义的,因此如果在程序中使用这些控制符必须把头文件 iomanip.h包含进来
前面讨论的输入输出是以系统指定的标准设备(输入设备为键盘,输出设备为显示器)为对象的。
在实际应用中,常以磁盘文件作为对象,即从磁盘文件读取数据,将数据输入到磁盘文件
所谓 “文件”,一般值存储在外部介质上的数据的集合。
一批数据是以文件的形式存放在外部介质上的。
操作系统是以文件为单位对数据进行管理的,也就是说,如果想找存储在外部介质上的数据,必须先按文件名找到指定的文件,然后再从文件中读取数据,
要向外部介质上存储数据也必须先建立一个文件(以文件名标识),才能向它输出数据
根据文件中数据的组织形式,文件可分为ASCII文件 和 二进制文件。
ASCII文件也称文本文件,其每个字节存一个ASCII码,表示一个字符。
这样的文件使用比较方便,但占用的存储空间较大。
二进制文件,是把内存中的存储形式原样写到外存中。
使用起来可以节省外存空间和转换时间,但是它的一个字节不对应一个字符
为了实现文件的输入输出,首先要创建一个文件流,当把这个流和实际的文件箱关联时,就称为打开文件。
完成输入输出后要关闭这个文件,即取消文件和流的关联。
下面介绍C++的I/O流类库中提供的文件流类
在C++的I/O流类库中定义了几种文件流类,专门用于对磁盘文件的输入输出操作。
它们是:ifstream类(支持从磁盘文件的输入)、ofstream类(支持向磁盘文件的输出)和fstream类(支持对磁盘文件的输入输出)
由前面的知识可以知道在以标准设备为对象的输入输出中,必须定义流对象,如cin, cout就是流对象,C++是通过流对象进行输入输出的。
同理如果以磁盘文件为对象进行输入输出,也必须先定义一个文件流类的对象,通过文件流对象将数据从内存输出到磁盘文件或者从磁盘文件将数据输入到内存。
由于cin和cout已在 iostream.h中事先定义,所以用户不需自己定义就可以使用,但在文件流对象对磁盘文件进行操作时,文件流对象没有事先统一定义,
必须由用户自己定义。
文件流对象是用文件流类定义的,看下面的语句:
ofstream outfile; // 定义一个输出文件流对象 outfile
ifstream infile; // 定义一个输入文件流对象infile
需要注意的是:在程序中定义文件流对象,必须包含头文件 fstream.h
磁盘文件的打开和关闭使用 fstream类中定义的成员函数 open 和 close
要对磁盘文件进行读写操作,首先必须要先打开文件。
所闻打开文件就是将文件流对象与具体的磁盘文件建立联系,并指定相应的使用方式。以上工作可以通过以下两种不同的方法实现。
1、先说明一个fstream类的对象,再调用该对象的成员函数 open打开指定的文件
例如,以输出方式打开一个文件的方法如下
ofstream outfile; // 定义ofstream类(输出文件流类)对象 outfile
outfile.open("f1.dat", ios::out); // 使文件流与 f1.dat 文件建立关联
上面第1句定义 ofstream类对象 outfile, 第2句通过 outfile嗲用其成员函数 open, 提供两个实参:
第1个实参时要被打开的文件名,使用文件名时可以包含路径,如 “c:\new\f1.dat”,如果默认路径,则默认为当前目录下的文件
第2个实参说明文件的访问方式,文件访问方式多种,如表9-4所示
方式名 | 用途 |
---|---|
ios::in | 以输入(读)方式打开文件 |
ios::out | 以输出(写)方式打开文件(默认方式),如果已有此名字的文件,则其原有内容全部清除 |
ios::app | 以追加方式打开文件,新增加的内容添加在文件尾 |
ios::ate | 文件打开时,文件指针定位于文件尾 |
ios::trunc | 如果文件存在,将其清除;如果文件不存在,创建新文件 |
ios::binary | 以二进制文件 打开文件,缺省时为文本文件 |
ios::nocreate | 打开已有文件,若文件不存在,则打开失败 |
ios::noreplace | 若打开的文件已经存在,则打开失败 |
当结束一个磁盘文件的读写操作后,应关闭该文件。
关闭文件用成员函数 close
如 “outfile.close();” 看下面的例子
如果文件的第一个字节均以ASCII码文件形式存放数据,即一个字节存放一个字符,这个文件就是ASCII文件(或称文本文件、字符文件)。
程序可以从ASCII文件中读取若干个字符,也可以向它输出一些字符
对文本文件的读写操作可以用 流插入运算符 “<<” 和 流提取运算符 “>>” 输入输出标准类型的数据
“<<” 和 “>>” 运算符已经在 iostream 中被重载,
C++处理异常的机制由3个部分组成:检查(try), 抛出(throw)和捕捉(catch)
把需要检查的语句放在 try块中,throw用来当出现异常时,抛出一个异常信息,而catch则用来捕捉异常信息,
如果捕捉到了异常信息,就处理它。
try-throw-catch构成了C++异常处理的基本结构,形式如下
try {
...
if(表达式1) throw x1;
...
if(表达式2) throw x2;
...
if(表达式n) throw xn;
...
}
catch(异常类型声明1) {
// 异常处理语句序列1
}
catch(异常类型声明2) {
// 异常处理语句序列2
}
...
catch(异常类型声明n) {
// 异常处理语句序列n
}
在一个try块中可以嵌套另一个try块。
每个try块都有自己的一组catch块,来处理该try块中抛出的异常。
try块的 catch块只能处理在该 try块中抛出的异常
异常处理可以局部化为一个函数,当每次进行该函数的调用时,异常将被重置。
这样编写程序能够简单明了,避免重复。
在处理异常检测时,也可以将抛掷异常的程序代码放在一个函数中,将检测处理异常的函数代码放在另一个函数中,
能让异常处理更具灵活性和实用性
为便于阅读程序,使用户在看程序时能够知道所用的函数是否会抛出异常信息以及抛出的异常信息的类型,
C++允许在函数声明时指定函数抛出的异常信息的类型,如
double Deta(double, double, double) throw(double);
表示 Deta函数只能抛出 double 类型的异常类型。如果写成:
double Deta(double, double, double) throw(int, float, double, char);
则表示 Deta函数 可以抛出 int, float, double 或 char类型的异常信息
异常指定是函数函数声明的一部分,必须同时出现在函数声明和函数定义的首行中,否则编译时,编译系统会报告 “类型不匹配”.
如果在函数声明时,不指定函数抛出的异常信息的类型,则该函数可以抛出任何类型的异常
int Func(int, char); // 函数Func 可以抛出任何异常
如果在函数声明时,指定 throw参数表为不包括任何类型的空表,则不允许函数抛出任何异常,如:
int Func(int, char) throw(); // 不允许函数Func抛出任何异常
这时,即使在函数中出现了 throw语句,实际上在函数执行出现异常时,也不执行 throw语句,并不抛出任何异常类型,
程序将非正常终止
构造函数是一个特殊的函数,对象创建时,构造函数自动被调用
如果构造函数中出现了问题,抛出了异常,会发生什么情况?
用来传递异常信息的类就是异常类。
异常类非常简单,甚至没有任何成员;
也可以同普通类一样复杂,有自己的数据成员、成员函数、构造函数、析构函数、虚函数等,
还可以通过派生方式构成异常类的继承层次结构。
下面我们首先看例10-6,程序中声明了一个没有任何成员的简单异常类
例10-6程序中声明了一个同普通类一样的有数据成员和成员函数的较复杂的异常类
由异常类建立的对象称为异常对象。
异常类的处理过程实际上就是异常对象的生成和传递过程。
在编写程序时,如果发生异常,则可以抛掷一个异常类对象,在catch语句中,则可以输出这个异常类对象的相关性新
TDM-GCC 4.9.2 64-bit Release
TDM-GCC 4.9.2 64-bit Debug
TDM-GCC 4.9.2 64-bit Profiling
TDM-GCC 4.9.2 32-bit Release
TDM-GCC 4.9.2 32-bit Debug
TDM-GCC 4.9.2 32-bit Profiling
http://www.cplusplus.com/
https://docs.microsoft.com/zh-cn/cpp/?view=vs-2019
用C++做的程序
\1. 服务器端开发:很多游戏或者互联网公司的后台服务器程序都是基于C++开发的,而且大部分是linux操作系统,所以说,你如果想做这样
的工作,需要熟悉linux操作系统及其在上面的开发,熟悉数据库开发,精通网络编程。
\2. 游戏:目前很多游戏客户端都是基于C++开发的,除了一些网页游戏可能不是,这个领域需要学习的东西就比较多,比如计算机图形、多媒体处理。
\3. 虚拟现实:这个领域一直在发展,目前VR眼镜比较火,需要大量基于这些的C++开发。
\4. 数字图像处理:比如像AutoCAD的系统开发,像OpenCV的视觉识别等等。
5.科学计算
在科学计算领域,FORTRAN是使用最多的语言之一。但是近年来,C++凭借先进的数值计算库、泛型编程等优势在这一领域也应用颇多。
6.网络软件
C++拥有很多成熟的用于网络通信的库,其中最具有代表性的是跨平台的、重量级的ACE库,该库可以说是C++语言最重要的成果之一,在许多重要的企业、部门甚至是军方都有应用。比如GOOGLE的chrome浏览器,就是使用C++开发。
7.分布式应用。
8.操作系统
在该领域,C语言是主要使用的编程语言。但是C++凭借其对C的兼容性,面向对象性质也开始在该领域崭露头角。
9.设备驱动程序
也是因为效率的原因。
10.移动(手持)设备。
11.嵌入式系统。
12.教育与科研。
13.部分行业应用。
1.如果可以增强指南的可读性,则允许对其进行任何违反
2.如果有强烈的反对意见,则可能违反规则。
3.代表类型的名称必须以大写字母混合使用。
3.代表类型的名称必须以大写字母混合使用。
5.命名常量(包括枚举值)必须全部使用大写字母,并使用下划线分隔单词。
6.表示方法或函数的名称必须为动词,并以小写开头的混合大小写形式。
7.代表名称空间的名称应全部小写。
8.表示模板类型的名称应为单个大写字母。
9.当用作名称[4]时,缩写词和首字母缩写词不能大写。
10.始终应使用::运算符引用全局变量。
11.私有类变量应带有下划线后缀
12.泛型变量应与其类型具有相同的名称。
13.所有名称均应以英文书写。
14.具有大范围的变量应具有长名称,具有小范围的变量可以具有短名称[1]。
15.对象的名称是隐式的,应避免在方法名称中使用。
17.必须在直接访问属性的地方使用术语“获取/设置”。
18.“计算”一词可用于计算某些内容的方法。
19.查找一词可用于查找某些内容的方法。
20.在建立对象或概念的地方可以使用术语初始化。
21.代表GUI组件的变量应在组件类型名称后缀。
22.代表对象集合的名称应使用复数形式。
23.前缀n应该用于表示多个对象的变量。
24.后缀No应该用于表示实体编号的变量。
25.迭代器变量应称为i,j,k等。
26.前缀是应该用于布尔变量和方法。
27.补码名称必须用于补码运算[1]。
28.应避免名称缩写。
29.应该特别避免命名指针。
30.必须避免取反布尔变量名。
31.枚举常量可以以通用类型名称作为前缀。
32.异常类应带有Exception后缀。
33.函数(返回某种东西的方法)应该以它们返回的名字命名,而程序(void方法)则以它们的名字命名。
\34. C ++头文件应具有扩展名*.h*(首选)或 .hpp。源文件可以具有扩展名*.c ++(推荐),.C*,.cc或 .cpp。
35.一个类应该在头文件中声明,并在源文件中定义,其中文件名与类名匹配。
36.所有定义都应位于源文件中。
37.文件内容必须保留在80列之内。
38.必须避免使用TAB和分页符之类的特殊字符。
39.分割线的不完整性必须明显[1]。
40.头文件必须包含一个包含保护。
41.包含语句应进行排序和分组。按照它们在系统中的分层位置进行排序,首先包含低级文件。在包含语句组之间留空行。
42. include语句必须仅位于文件顶部。
43.只能在一个文件中声明本地类型。
44.类的各个部分必须按public,protected 和private [2] [3]排序。必须明确标识所有部分。不适用的部分应省略。
45.类型转换必须始终明确进行。永远不要依赖隐式类型转换。
46.变量应在声明的地方初始化。
47.变量绝不能具有双重含义。
48.应尽量减少使用全局变量。
49.绝对不应将类变量声明为公共变量。
51. C ++指针和引用应在类型旁边而不是名称旁边带有引用符号。
53.除布尔变量和指针外,不应使用隐式测试0。
54.变量应在尽可能小的范围内声明。
55.仅循环控制语句必须包含在for()
构造中。
56.循环变量应在循环之前立即初始化。
57.可以避免do-while
循环。
58.应避免使用中断
和继续
循环。
while(true)
形式应用于无限循环。61.必须避免复杂的条件表达式。引入临时布尔变量[1]。
62.名义上的大小写应该放在if语句[1]的if -part中,而例外则放在else -part中。
63.有条件的应放在单独的行上。
64.必须避免条件语句中的可执行语句。
65.应避免在代码中使用幻数。应该将0和1以外的数字声明为命名常量。
66.浮点常数应始终写有小数点和至少一个小数。
67.浮点常数应始终在小数点前写一个数字。
68.函数必须始终明确列出返回值
69.不应使用goto
。
70.应该使用“ 0”代替“ NULL”。
71.基本缩进应为2。
72.块布局应如下面的示例1(推荐)或示例2所示,并且不得与示例3 [4]所示。功能块和类块必须使用示例2的块布局。
73.类
声明应采用以下形式
74.方法定义应采用以下形式:
if-else
类的语句应具有以下形式\76. for
语句应采用以下形式
77.空的for
语句应具有以下形式
\78. while
语句应采用以下形式
79.做时
声明应采用以下形式
\80. switch
语句应具有以下形式
\81. try-catch
语句应具有以下形式
82.单个语句if-else
,for
或while
语句可以不用括号括起来
83.函数返回类型可以放在函数名称正上方的左列中
84.-
常规运算符应由空格字符包围。
-C ++保留字后面应有空格。
-逗号后应加上空格。
-冒号应被空白包围。
-陈述中的分号后应带有空格。
85.方法名称后可以有一个空格,后跟另一个名称。
86.块中的逻辑单元应由一个空白行分隔。
87.方法应由三个空白行分隔。
88.声明中的变量可以左对齐
89.在提高可读性的地方使用对齐方式
90.棘手的代码不应被注释,而必须重写![1]
91.所有评论均应以英文撰写[2]。
92.对所有注释(包括多行注释)使用*//*。
93.注释应包括在代码中的位置。[1]
94.类和方法头的注释应遵循JavaDoc约定。
Boost库 是为 C++语言标准库提供扩展的一些C++程序库的总称,由Boost社区组织开发、维护
可以与C++标准库完美共同工作,并且为其提供扩展功能
我们在开发程序过程中,会用到一些与编译有关的术语,比如:【编辑器、编译器、调试器、连接器,链接器、解释器,集成开发环境(Integrated Development Environment,IDE)、图形用户界面工具】等,他们都是什么含义?
程序开发中的“编辑器”一般是指“代码编辑器”。一般而言,计算机程序是用文本形式体现的。少量专门用途的“编程语言”可能是用图形界面编写程序的,比如用于幼儿编程教育的 Alice 等。
代码编辑器主要用于用来编写和查看程序源代码。通常这种编辑器有语法加亮(Syntax-Highlighting)功能。
【中文维基百科】:编译器(compiler),是一种计算机程序,它会将用某种编程语言写成的源代码(原始语言),转换成另一种编程语言(目标语言)。
【英文维基百科】:A compiler is a computer program that transforms computer code written in one programming language (the source language) into another programming language (the target language). Compilers are a type of translator that support digital devices, primarily computers. The name compiler is primarily used for programs that translate source code from a high-level programming language to a lower level language (e.g., assembly language, object code, or machine code) to create an executable program.
【中文维基百科】:是一个程序,将一个或多个由编译器或汇编器生成的目标文件外加库链接为一个可执行文件
Linker 有时翻译为链接器,有时翻译为连接器。但是后者比较少见。
【中文维基百科】:调试器是指一种用于调试其它程序的计算机程序及工具。能够让代码在指令组模拟器(ISS)中可以检查运行状况以及选择性地运行,以便排错、调试。当开发的进度遇到瓶颈或找不出哪里有问题时,这技术将是非常有用的。但是将程序运行在调试器之下,这将比直接在运作的平台以及处理器上运行还要来得慢。
典型的调试器通常能够在程序运行时拥有以下这些功能,例如单步运行(single-stepping)、利用中断点(breakpoint)使程序遇到各种种类的事件(event)时停止(breaking)(一般用于使程序停止在想要检查的状态)、以及追踪某些变量的变化。有些调试器也有能力在想要调试的程序在运行状态时,去改变它的状态,而不仅仅只是用来观察而己。
【中文维基百科】:解释器是一种计算机程序,能够把高级编程语言一行一行解释运行。解释器像是一位“中间人”,每次运行程序时都要先转成另一种语言再作运行,因此解释器的程序运行速度比较缓慢。它不会一次把整个程序翻译出来,而是每翻译一行程序就立刻运行,然后再翻译下一行,再运行,如此不停地进行下去。
【中文维基百科】:集成开发环境是一种辅助程序开发人员开发软件的应用软件,在开发工具内部就可以辅助编写源代码文本、并编译打包成为可用的程序,有些甚至可以设计图形接口。
IDE通常包括编程语言编辑器、自动构建工具、通常还包括调试器。有些IDE包含编译器/解释器,如微软的Microsoft Visual Studio,有些则不包含,如Eclipse、SharpDevelop等,这些IDE是通过调用第三方编译器来实现代码的编译工作的。有时IDE还会包含版本控制系统和一些可以设计图形用户界面的工具。许多支持面向对象的现代化IDE还包括了类别浏览器、对象查看器、对象结构图。虽然当前有一些IDE支持多种编程语言(例如Eclipse、NetBeans、Microsoft Visual Studio),但是一般而言,IDE主要还是针对特定的编程语言而量身打造(例如Visual Basic)。
图形用户界面(Graphical User Interface,简称GUI)是指采用图形方式显示的计算机操作用户界面。与早期计算机使用的命令行界面相比,图形界面对于用户来说在视觉上更易于接受。然而这界面若要透过在显示器的特定位置,以“各种美观、而不单调的视觉消息”提示用户“状态的改变”,势必得比简单的文字消息呈现,花上更多的计算机运算能力,计算“要改变显示器哪些光点,变成哪些颜色”
【英文维基百科】:In computing, a visual programming language (VPL) is any programming language that lets users create programs by manipulating program elements graphically rather than by specifying them textually. (在计算领域,可视化程序设计语言是任何能让用户以图形化的方式操纵程序元素,而不是仅仅使用编写文本的方式,来生成程序的语言)
常见的可视化程序设计语言,通常都是与集成开发环境打包的。比如 C++ 本身不是可视化程序设计语言,但是 Visual C++ 中提供了为 Windows 窗口应用程序创建界面的功能,所以以一种不严格的说法,Visual C++ 是可视化程序设计语言。
我们很难听到“图形用户界面设计工具”这样的说法。
一种可能的理解是,集成开发环境为可视化程序设计语言提供的用于设计图形用户界面的功能或者工具。
另一种可能的理解是指“原型设计工具”,比如Axure等。
----------------
其它参考资料
符合C++11及之后标准的C++称之为“Modern C++”,即“现代C++”。之前的C++称为“Classic C++”,即“经典C++”
年份 | C++标准 | C++标准 |
---|---|---|
1998 | ISO/IEC 14882:1998 | C++98 |
2003 | ISO/IEC 14882:2003 | C++03 |
2011 | ISO/IEC 14882:2011 | C++11, C++0x |
2014 | ISO/IEC 14882:2014 | C++14, C++1y |
2017 | ISO/IEC 14882:2017 | C++17, C++1z |
2020 | ISO/IEC 14882:2020 | C++20 |
英文版: 《Introduction to Programming with C++》(3rd Ed.), Y. Daniel Liang,机械工业出版社,2013年6月1日出版
中文版:《C++程序设计》(第3版),梁勇,机械工业出版社, 2015年1月1日出版
理念:fundamentals-first
适于无任何基础的初学者
作者Stanley B. Lippman等
C++大全,深度适当,适合自学
太厚(848), 很多读者半道出家去学java
第五版按C++11标准更新了内容,示例均采用 C++11 标准改写
理念:Object-Early
2nd edition, Volume 1, Bruce Eckel
免费电子版(英文): https://mindview.net/Books/TICPP/ThinkingInCPP2e.html
作者:Stanley B. Lippman
适于有编程基础的人
作者:Bjarne Stroustrup
一本每个人都应该买但不一定要看的书
可以当字典用,不适用于初学者
作者:Nicolai M. Josuttis;译者:侯捷
工欲善其事,必先利其器:利用标准库,提高实作能力
作者:Lippman;译者:侯捷
从编译器的角度来探讨C++的实现方法和优缺点
新版工具提供更友好的提示信息
新版工具提供更便捷的操作
(1) Visual Studio Community (C++)
(2) Eclipse CDT + Gcc
(3) Visual Studio Code + Gcc/Clang
(4) Xcode (Mac)
Visual Studio Community (C++)
Eclipse CDT + Gcc
Visual Studio Code + cl (msvc compiler)
Gcc 8.2 vs Gcc 4.9