本文总结了几乎所有不易理解或是容易忘记的C++知识,可作为手册查阅,内容参考自清华大学郑莉教授的C++课程。
声明时使用关键字 inline
编译时在调用处用函数体进行替换,节省了参数传递、控制转移等开销
注意:
内联函数体内不能有循环语句和switch语句
内联函数的定义必须出现在内联函数第一次被调用之前
对内联函数不能进行异常接口声明
定义内联函数,可以显式用inline声明,也可以直接在类内定义好实现
编译器并不一定遵从我们的inline
constexpr修饰的函数在其所有参数都是constexpr时,一定返回constexpr
函数体中必须有且仅有一条return语句
constexpr的变量的值必须是编译器在编译的时候就可以确定的
constexpr int get_size() { return 20; }
constexpr int foo = get_size(); //正确:foo是一个常量表达式
通过形参的个数不同或者类型不同进行区分
无法通过返回值区分
//下面两个都是默认构造函数,如在类中同时出现,将产生编译错误:
Clock();
Clock(int newH=0,int newM=0,int newS=0);
如果程序中未定义构造函数,编译器将在需要时自动生成一个默认构造函数
参数列表为空,不为数据成员设置初始值
如果类内定义了成员的初始值,则使用类内定义的初始值
如果没有定义类内的初始值,则以默认方式初始化
基本类型的数据默认初始化的值是不确定的
如果程序中已定义构造函数,默认情况下编译器就不再隐含生成默认构造函数。如果此时依然希望编译器隐含生成默认构造函数,可以使用=default
class Clock {
public:
Clock() =default; //指示编译器提供默认构造函数
Clock(int newH, int newM, int newS); //构造函数
private:
int hour, minute, second;
};
类中往往有多个构造函数,只是参数表和初始化列表不同,其初始化算法都是相同的,这时,为了避免代码重复,可以使用委托构造函数
不使用委托构造函数:
//构造函数
Clock(int newH, int newM, int newS) : hour(newH),minute(newM),second(newS) {}
//默认构造函数
Clock() : hour(0),minute(0),second(0) {}
使用委托构造函数:
Clock(int newH, int newM, int newS) : hour(newH),minute(newM),second(newS) {}
Clock(): Clock(0, 0, 0) {}
复制构造函数是一种特殊的构造函数,其形参为本类的对象引用,作用是用一个已存在的对象去初始化同类型的新对象
定义一个对象时,以本类另一个对象作为初始值,发生复制构造
如果函数的形参是类的对象,调用函数时,将使用实参对象初始化形参对象,发生复制构造
如果函数的返回值是类的对象,函数执行完成返回主调函数时,将使用return语句中的对象初始化一个临时无名对象,传递给主调函数,此时发生复制构造
如果程序员没有为类声明拷贝初始化构造函数,则编译器自己生成一个隐含的复制构造函数
这个构造函数执行的功能是:用作为初始值的对象的每个数据成员的值,初始化将要建立的对象的对应数据成员(浅拷贝)
C++11做法:用=delete
指示编译器不生成默认复制构造函数。
class Point { //Point 类的定义
public:
Point(int xx=0, int yy=0) { x = xx; y = yy; } //构造函数,内联
Point(const Point& p) =delete; //指示编译器不生成默认复制构造函数
private:
int x, y; //私有数据
};
首先对构造函数初始化列表中列出的成员(包括基本类型成员和对象成员)进行初始化,初始化次序是成员在类体中定义的次序
成员对象构造函数调用顺序:按对象成员的声明顺序,先声明者先构造
初始化列表中未出现的成员对象:调用用默认构造函数(即无形参的)初始化
处理完初始化列表之后,再执行构造函数的函数体
如果需要在某个类的声明之前,引用该类,则应进行前向引用声明
前向引用声明只为程序引入一个标识符,但具体声明在其他地方
class B; //前向引用声明
class A {
public:
void f(B b);
};
class B {
public:
void g(A a);
};
使用前向引用声明虽然可以解决一些问题,但它并不是万能的
在提供一个完整的类声明之前,不能声明该类的对象,也不能在内联成员函数中使用该类的对象
当使用前向引用声明时,只能使用被声明的符号,而不能涉及类的任何细节
class Fred; //前向引用声明
class Barney {
Fred x; //错误:类Fred的声明尚不完善
};
class Fred {
Barney y;
};
成员共用同一组内存单元
任何两个成员不会同时有效
class ExamInfo {
private:
string name; //课程名称
enum { GRADE, PASS, PERCENTAGE } mode;//计分方式
union {
char grade; //等级制的成绩
bool pass; //只记是否通过课程的成绩
int percent; //百分制的成绩
};
public:
//三种构造函数,分别用等级、是否通过和百分初始化
ExamInfo(string name, char grade)
: name(name), mode(GRADE), grade(grade) { }
ExamInfo(string name, bool pass)
: name(name), mode(PASS), pass(pass) { }
ExamInfo(string name, int percent)
: name(name), mode(PERCENTAGE), percent(percent) { }
void show();
}
void ExamInfo::show() {
cout << name << ": ";
switch (mode) {
case GRADE: cout << grade; break;
case PASS: cout << (pass ? "PASS" : "FAIL"); break;
case PERCENTAGE: cout << percent; break;
}
cout << endl;
}
int main() {
ExamInfo course1("English", 'B');
ExamInfo course2("Calculus", true);
ExamInfo course3("C++ Programming", 85);
course1.show();
course2.show();
course3.show();
return 0;
}
//运行结果:
//English: B
//Calculus: PASS
//C++ Programming: 85
//enum class 枚举类型名: 底层类型 {枚举值列表};
enum class Type { General, Light, Medium, Heavy};
enum class Type: char { General, Light, Medium, Heavy};
enum class Category { General=1, Pistol, MachineGun, Cannon};
枚举类的优势
强作用域,其作用域限制在枚举类中
转换限制,枚举类对象不可以与整型隐式地互相转换。
可以指定底层类型
#include<iostream>
using namespace std;
enum class Side{ Right, Left };
enum class Thing{ Wrong, Right }; //不冲突
int main()
{
Side s = Side::Right;
Thing w = Thing::Wrong;
cout << (s == w) << endl; //编译错误,无法直接比较不同枚举类
return 0;
}
友元是C++提供的一种破坏数据封装和数据隐藏的机制
通过将一个模块声明为另一个模块的友元,一个模块能够引用到另一个模块中本是被隐藏的信息
为了确保数据的完整性,及数据封装与隐藏的原则,建议尽量不使用或少使用友元
友元函数是在类声明中由关键字friend修饰说明的非成员函数,在它的函数体中能够通过对象名访问 private 和protected成员
作用:增加灵活性,使程序员可以在封装和快速性方面做合理选择
访问对象中的成员必须通过对象名
若一个类为另一个类的友元,则此类的所有成员都能访问对方类的私有成员
声明语法:将友元类名在另一个类中使用friend修饰说明
如果声明B类是A类的友元,B类的成员函数就可以访问A类的私有和保护数据,但A类的成员函数却不能访问B类的私有、保护数据
对于既需要共享、又需要防止改变的数据应该声明为常类型(用const进行修饰)
const关键字可以被用于参与对重载函数的区分
通过常对象只能调用它的常成员函数
#include<iostream>
using namespace std;
class R {
public:
R(int r1, int r2) : r1(r1), r2(r2) { }
void print();
void print() const;
private:
int r1, r2;
};
void R::print() {
cout << r1 << ":" << r2 << endl;
}
void R::print() const {
cout << r1 << ";" << r2 << endl;
}
int main() {
R a(5,4);
a.print(); //调用void print()
const R b(20,52);
b.print(); //调用void print() const
return 0;
}
常成员函数可以被非常对象调用,但常对象不可调用非常成员函数
如果一个变量除了在定义它的源文件中可以使用外,还能被其它文件使用,那么就称这个变量是外部变量
文件作用域中定义的变量,默认情况下都是外部变量,但在其它文件中如果需要使用这一变量,需要用extern关键字加以声明
在所有类之外声明的函数(也就是非成员函数),都是具有文件作用域的
这样的函数都可以在不同的编译单元中被调用,只要在调用之前进行引用性声明(即声明函数原型)即可。也可以在声明函数原型或定义函数时用extern修饰,其效果与不加修饰的默认状态是一样的
预处理在编译前进行
每条预处理指令必须单独占用一行
预处理指令可以出现在程序的任何位置
不能通过指向常量的指针改变所指对象的值,但指针本身可以改变,可以指向另外的对象。
int a;
const int *p1 = &a; //p1是指向常量的指针
int b;
p1 = &b; //正确,p1本身的值可以改变
*p1 = 1; //编译时出错,不能通过p1改变所指的对象
若声明指针常量,则指针本身的值不能被改变。
int a;
int * const p2 = &a;
p2 = &b; //错误,p2是指针常量,值不能改变
int f(int a, int b) {
return a + b;
}
int main() {
int (*p)(int, int) = f;
cout<<p(1, 2)<<endl;
return 0;
}
显式管理内存在是能上有优势,但容易出错
C++11提供智能指针的数据类型,对垃圾回收技术提供了一些支持,实现一定程度的内存管理
移动构造可以减少不必要的复制,带来性能上的提升
C++11之前,如果要将源对象的状态转移到目标对象只能通过复制。在某些情况下,我们没有必要复制对象——只需要移动它们
有可被利用的临时对象时,触发移动构造
//函数返回含有指针成员的对象
//将要返回的局部对象转移到主调函数,省去了构造和删除临时对象的过程
#include<iostream>
using namespace std;
class IntNum {
public:
IntNum(int x = 0) : xptr(new int(x)){ //构造函数
cout << "Calling constructor..." << endl;
}
IntNum(const IntNum & n) : xptr(new int(*n.xptr)){//复制构造函数
cout << "Calling copy constructor..." << endl;
}
//&&是右值引用
//函数返回的临时变量是右值
IntNum(IntNum && n): xptr(n.xptr){ //移动构造函数
n.xptr = nullptr;
cout << "Calling move constructor..." << endl;
}
~IntNum(){ //析构函数
delete xptr;
cout << "Destructing..." << endl;
}
private:
int *xptr;
};
//返回值为IntNum类对象
IntNum getNum() {
IntNum a;
return a;
}
int main() {
cout << getNum().getInt() << endl; return 0;
}
/*
运行结果:
Calling constructor...
Calling move constructor...
Destructing... //这里释放了nullptr
0
Destructing...
*/
左值和右值都是针对表达式而言的
左值是指表达式结束后依然存在的持久对象
右值指表达式结束时就不再存在的临时对象——显然右值不可以被取地址
用cin的>>操作符输入字符串,会以空格作为分隔符,空格后的内容会在下一回输入时被读取
getline可以输入整行字符串(要包string头文件),例如:getline(cin, s2);
输入字符串时,可以使用其它分隔符作为字符串结束的标志(例如逗号、分号),将分隔符作为getline的第3个参数即可,例如:getline(cin, s2, ',');
#include <iostream>
#include <string>
using namespace std;
int main() {
for (int i = 0; i < 2; i++){
string city, state;
getline(cin, city, ',');
getline(cin, state);
cout << "City:" << city << “ State:" << state << endl;
}
return 0;
}
/*
运行结果:
Beijing,China
City: Beijing State: China
San Francisco,the United States
City: San Francisco State: the United States
*/
继承的访问控制
访问权限
继承的访问控制
访问权限
继承的访问控制
访问权限
protected 成员的特点与作用
class A{
public:
void setA(int);
private:
int a;
};
class B{
public:
void setB(int);
private:
int b;
};
class C:public A, private B{
public:
void setC(int, int, int);
private:
int c;
};
void A::setA(int x){
a = x;
}
void B::setB(int x){
b = x;
}
void C::setC(int x, int y, int z){
setA(x);
setB(y);
c = z;
}
int main(int argc, const char * argv[]) {
C obj;
obj.setA(5); // 正确
obj.setB(6); // 错误
obj.setC(6, 7, 9); // 正确
return 0;
}
默认情况
C++11规定
派生类名::派生类名(形参表):
基类名1(参数), 基类名2(参数), ..., 基类名n(参数),
本类成员(含对象成员)初始化列表
{
//其他初始化
};
编译器会在需要时生成一个隐含的复制构造函数
先调用基类的复制构造函数
再为派生类新增的成员执行复制
一般都要为基类的复制构造函数传递参数
复制构造函数只能接受一个参数,既用来初始化派生类定义的成员,也将被传递给基类的复制构造函数
基类的复制构造函数形参类型是基类对象的引用,实参可以是派生类对象的引用
例如: C::C(const C &c1): B(c1) {…}
析构函数不被继承,派生类如果需要,要自行声明析构函数
声明方法与无继承关系时类的析构函数相同
不需要显式地调用基类的析构函数,系统会自动隐式调用
先执行派生类析构函数的函数体,再调用基类的析构函数
当派生类与基类中有相同成员时:
如果从不同基类继承了同名成员,但是在派生类中没有定义同名成员,“派生类对象名或引用名.成员名”、“派生类指针->成员名”访问成员存在二义性问题
需要解决的问题
虚基类声明
class B1:virtual public B
作用
注意:
建立对象时所指定的类称为最远派生类
虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的
在整个继承结构中,直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化表中为虚基类的构造函数列出参数。如果未列出,则表示调用该虚基类的默认构造函数
在建立对象时,只有最远派生类的构造函数调用虚基类的构造函数,其他类对虚基类构造函数的调用被忽略
#include <iostream>
using namespace std;
class Base0 {
public:
Base0(int var) : var0(var) { }
int var0;
void fun0() { cout << "Member of Base0" << endl; }
};
class Base1: virtual public Base0 {
public:
Base1(int var) : Base0(var) { }
int var1;
};
class Base2: virtual public Base0 {
public:
Base2(int var) : Base0(var) { }
int var2;
};
class Derived: public Base1, public Base2 {
public:
Derived(int var) : Base0(var), Base1(var), Base2(var) { }
int var;
void fun()
{ cout << "Member of Derived" << endl; }
};
int main() { //程序主函数
Derived d(1);
d.var0 = 2; //直接访问虚基类的数据成员
d.fun0(); //直接访问虚基类的函数成员
return 0;
}
如果要重载 B 为类成员函数,使之能够实现表达式 oprd1 B oprd2,其中 oprd1 为A 类对象,则 B 应被重载为 A 类的成员函数,形参类型应该是 oprd2 所属的类型
经重载后,表达式 oprd1 B oprd2 相当于 oprd1.operator B(oprd2)
#include <iostream>
using namespace std;
class Complex {
public:
Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) { }
//运算符+重载成员函数
Complex operator + (const Complex &c2) const;
//运算符-重载成员函数
Complex operator - (const Complex &c2) const;
void display() const; //输出复数
private:
double real; //复数实部
double imag; //复数虚部
};
//复数类加减法运算重载为成员函数
Complex Complex::operator + (const Complex &c2) const{
//创建一个临时无名对象作为返回值
return Complex(real+c2.real, imag+c2.imag);
}
Complex Complex::operator - (const Complex &c2) const{
//创建一个临时无名对象作为返回值
return Complex(real-c2.real, imag-c2.imag);
}
void Complex::display() const {
cout<<"("<<real<<", "<<imag<<")"<<endl;
}
//复数类加减法运算重载为成员函数
int main() {
Complex c1(5, 4), c2(2, 10), c3;
cout << "c1 = "; c1.display();
cout << "c2 = "; c2.display();
c3 = c1 - c2; //使用重载运算符完成复数减法
cout << "c3 = c1 - c2 = "; c3.display();
c3 = c1 + c2; //使用重载运算符完成复数加法
cout << "c3 = c1 + c2 = "; c3.display();
return 0;
}
如果要重载 U 为类成员函数,使之能够实现表达式 U oprd,其中 oprd 为A类对象,则 U 应被重载为 A 类的成员函数,无形参。
经重载后,表达式 U oprd 相当于 oprd.operator U()
如果要重载 ++或--为类成员函数,使之能够实现表达式 oprd++ 或 oprd-- ,其中 oprd 为A类对象,则 ++或-- 应被重载为 A 类的成员函数,且具有一个 int 类型形参。
经重载后,表达式 oprd++ 相当于 oprd.operator ++(0)
#include <iostream>
using namespace std;
class Clock {//时钟类定义
public:
Clock(int hour = 0, int minute = 0, int second = 0);
void showTime() const;
//前置单目运算符重载
Clock& operator ++ ();
//后置单目运算符重载
Clock operator ++ (int);
private:
int hour, minute, second;
};
Clock::Clock(int hour, int minute, int second) {
if (0 <= hour && hour < 24 && 0 <= minute && minute < 60
&& 0 <= second && second < 60) {
this->hour = hour;
this->minute = minute;
this->second = second;
} else
cout << "Time error!" << endl;
}
void Clock::showTime() const { //显示时间
cout << hour << ":" << minute << ":" << second << endl;
}
//重载前置++和后置++为时钟类成员函数
Clock & Clock::operator ++ () {
second++;
if (second >= 60) {
second -= 60; minute++;
if (minute >= 60) {
minute -= 60; hour = (hour + 1) % 24;
}
}
return *this;
}
Clock Clock::operator ++ (int) {
//注意形参表中的整型参数
Clock old = *this;
++(*this); //调用前置“++”运算符
return old;
}
int main() {
Clock myClock(23, 59, 59);
cout << "First time output: ";
myClock.showTime();
cout << "Show myClock++: ";
(myClock++).showTime();
cout << "Show ++myClock: ";
(++myClock).showTime();
return 0;
}
有些运算符不能重载为成员函数,例如二元运算符的左操作数不是对象,或者是不能由我们重载运算符的对象
//重载Complex的加减法和“<<”运算符为非成员函数
//将+、-(双目)重载为非成员函数,并将其声明为复数类的友元,两个操作数都是复数类的常引用。 • 将<<(双目)重载为非成员函数,并将其声明为复数类的友元,它的左操作数是std::ostream引用,右操作数为复数类的常引用,返回std::ostream引用
#include <iostream>
using namespace std;
class Complex {
public:
Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) { }
friend Complex operator+(const Complex &c1, const Complex &c2);
friend Complex operator-(const Complex &c1, const Complex &c2);
friend ostream & operator<<(ostream &out, const Complex &c);
private:
double real; //复数实部
double imag; //复数虚部
};
Complex operator+(const Complex &c1, const Complex &c2){
return Complex(c1.real+c2.real, c1.imag+c2.imag);
}
Complex operator-(const Complex &c1, const Complex &c2){
return Complex(c1.real-c2.real, c1.imag-c2.imag);
}
ostream & operator<<(ostream &out, const Complex &c){
out << "(" << c.real << ", " << c.imag << ")";
return out;
}
int main() {
Complex c1(5, 4), c2(2, 10), c3;
cout << "c1 = " << c1 << endl;
cout << "c2 = " << c2 << endl;
c3 = c1 - c2; //使用重载运算符完成复数减法
cout << "c3 = c1 - c2 = " << c3 << endl;
c3 = c1 + c2; //使用重载运算符完成复数加法
cout << "c3 = c1 + c2 = " << c3 << endl;
return 0;
}
为什么需要虚析构函数? - 可能通过基类指针删除派生类对象; - 如果你打算允许其他人通过基类指针调用对象的析构函数(通过delete这样做是正常的),就需要让基类的析构函数成为虚函数,否则执行delete的结果是不确定的
#include <iostream>
using namespace std;
class Base{
public:
virtual ~Base();
};
Base::~Base()
{
cout << "Base ";
}
class Derived: public Base{
public:
virtual ~Derived();
};
Derived::~Derived(){
cout << "Derived ";
}
void fun(Base *b){
delete b;
}
int main(int argc, const char * argv[]) {
Base *b = new Derived();
fun(b);
return 0;
}
虚表
动态绑定的实现
class A{
public:
virtual void fun();
};
//在32位机器上,sizeof(A)为:4;在64位机器上,sizeof(A)为:8
//因为A中含有一个指向虚表的指针,在32位机器上,指针占4个字节;在64位机器上,指针占8个字节
纯虚函数是一个在基类中声明的虚函数,它在该基类中没有定义具体的操作内容,要求各派生类根据实际需要定义自己的版本,纯虚函数的声明格式为:virtual 函数类型 函数名(参数表) = 0;
带有纯虚函数的类称为抽象类
注意:
#include <iostream>
using namespace std;
class Base1 {
public:
virtual void display() const = 0; //纯虚函数
};
class Base2: public Base1 {
public:
virtual void display() const; //覆盖基类的虚函数
};
void Base2::display() const {
cout << "Base2::display()" << endl;
}
class Derived: public Base2 {
public:
virtual void display() const; //覆盖基类的虚函数
};
void Derived::display() const {
cout << "Derived::display()" << endl;
}
void fun(Base1 *ptr) {
ptr->display();
}
int main() {
Base2 base2;
Derived derived;
fun(&base2);
fun(&derived);
return 0;
}
C++11 引入显式函数覆盖,在编译期而非运行期捕获此类错误。 - 在虚函数显式重载中运用,编译器会检查基类是否存在一虚拟函数,与派生类中带有声明override的虚拟函数,有相同的函数签名(signature);若不存在,则会回报错误
C++11提供final,用来避免类被继承,或是基类的函数被改写 例:
struct Base1 final { };
struct Derived1 : Base1 { }; // 编译错误:Base1为final,不允许被继承
struct Base2 { virtual void f() final; };
struct Derived2 : Base2 { void f(); // 编译错误:Base2::f 为final,不允许被覆盖 };
语法形式:
template <模板参数表>
模板参数表的内容:
注意:
#include <iostream>
using namespace std;
template <class T> //定义函数模板
void outputArray(const T *array, int count) {
for (int i = 0; i < count; i++)
cout << array[i] << " "; //如果数组元素是类的对象,需要该对象所属类重载了流插入运算符“<<”
cout << endl;
}
int main() {
const int A_COUNT = 8, B_COUNT = 8, C_COUNT = 20;
int a [A_COUNT] = { 1, 2, 3, 4, 5, 6, 7, 8 };
double b[B_COUNT] = { 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8 };
char c[C_COUNT] = "Welcome!";
cout << " a array contains:" << endl;
outputArray(a, A_COUNT);
cout << " b array contains:" << endl;
outputArray(b, B_COUNT);
cout << " c array contains:" << endl;
outputArray(c, C_COUNT);
return 0;
}
使用类模板使用户可以为类声明一种模式,使得类中的某些数据成员、某些成员函数的参数、某些成员函数的返回值,能取任意类型(包括基本类型的和用户自定义类型)
类模板 template <模板参数表> class 类名 {类成员声明};
如果需要在类模板以外定义其成员函数,则要采用以下的形式: template <模板参数表> 类型名 类名<模板参数标识符列表>::函数名(参数表)
#include <iostream>
#include <cstdlib>
using namespace std;
struct Student {
int id; //学号
float gpa; //平均分
};
template <class T>
class Store {//类模板:实现对任意类型数据进行存取
private:
T item; // item用于存放任意类型的数据
bool haveValue; // haveValue标记item是否已被存入内容
public:
Store();
T &getElem(); //提取数据函数
void putElem(const T &x); //存入数据函数
};
template <class T>
Store<T>::Store(): haveValue(false) { }
template <class T>
T &Store<T>::getElem() {
//如试图提取未初始化的数据,则终止程序
if (!haveValue) {
cout << "No item present!" << endl;
exit(1); //使程序完全退出,返回到操作系统。
}
return item; // 返回item中存放的数据
}
template <class T>
void Store<T>::putElem(const T &x) {
// 将haveValue 置为true,表示item中已存入数值
haveValue = true;
item = x; // 将x值存入item
}
int main() {
Store<int> s1, s2;
s1.putElem(3);
s2.putElem(-7);
cout << s1.getElem() << " " << s2.getElem() << endl;
Student g = { 1000, 23 };
Store<Student> s3;
s3.putElem(g);
cout << "The student id is " << s3.getElem().id << endl;
Store<double> d;
cout << "Retrieving object D... ";
cout << d.getElem() << endl;
//d未初始化,执行函数D.getElement()时导致程序终止
return 0;
}
自己实现一个动态数组
#ifndef ARRAY_H
#define ARRAY_H
#include <cassert>
template <class T> //数组类模板定义
class Array {
private:
T* list; //用于存放动态分配的数组内存首地址
int size; //数组大小(元素个数)
public:
Array(int sz = 50); //构造函数
Array(const Array<T> &a); //复制构造函数
~Array(); //析构函数
Array<T> & operator = (const Array<T> &rhs); //重载"=“
T & operator [] (int i); //重载"[]”
const T & operator [] (int i) const; //重载"[]”常函数
operator T * (); //重载到T*类型的转换
operator const T * () const;
int getSize() const; //取数组的大小
void resize(int sz); //修改数组的大小
};
template <class T> Array<T>::Array(int sz) {//构造函数
assert(sz >= 0);//sz为数组大小(元素个数),应当非负
size = sz; // 将元素个数赋值给变量size
list = new T [size]; //动态分配size个T类型的元素空间
}
template <class T> Array<T>::~Array() { //析构函数
delete [] list;
}
template <class T>
Array<T>::Array(const Array<T> &a) { //复制构造函数
size = a.size; //从对象x取得数组大小,并赋值给当前对象的成员
list = new T[size]; // 动态分配n个T类型的元素空间
for (int i = 0; i < size; i++) //从对象X复制数组元素到本对象
list[i] = a.list[i];
}
//重载"="运算符,将对象rhs赋值给本对象。实现对象之间的整体赋值
template <class T>
Array<T> &Array<T>::operator = (const Array<T>& rhs) {
if (&rhs != this) {
//如果本对象中数组大小与rhs不同,则删除数组原有内存,然后重新分配
if (size != rhs.size) {
delete [] list; //删除数组原有内存
size = rhs.size; //设置本对象的数组大小
list = new T[size]; //重新分配size个元素的内存
}
//从对象X复制数组元素到本对象
for (int i = 0; i < size; i++)
list[i] = rhs.list[i];
}
return *this; //返回当前对象的引用
}
//重载下标运算符,实现与普通数组一样通过下标访问元素,具有越界检查功能
template <class T>
T &Array<T>::operator[] (int n) {
assert(n >= 0 && n < size); //检查下标是否越界
return list[n]; //返回下标为n的数组元素
}
template <class T>
const T &Array<T>::operator[] (int n) const {
assert(n >= 0 && n < size); //检查下标是否越界
return list[n]; //返回下标为n的数组元素
}
//重载指针转换运算符,将Array类的对象名转换为T类型的指针
template <class T>
Array<T>::operator T * () {
return list; //返回当前对象中私有数组的首地址
}
//取当前数组的大小
template <class T>
int Array<T>::getSize() const {
return size;
}
// 将数组大小修改为sz
template <class T>
void Array<T>::resize(int sz) {
assert(sz >= 0); //检查sz是否非负
if (sz == size) //如果指定的大小与原有大小一样,什么也不做
return;
T* newList = new T [sz]; //申请新的数组内存
int n = (sz < size) ? sz : size;//将sz与size中较小的一个赋值给n
//将原有数组中前n个元素复制到新数组中
for (int i = 0; i < n; i++)
newList[i] = list[i];
delete[] list; //删除原数组
list = newList; // 使list指向新数组
size = sz; //更新size
}
#endif //ARRAY_H
迭代器是算法和容器的桥梁
算法和容器独立
#include <algorithm>
#include <iterator>
#include <vector>
#include <iostream>
using namespace std;
//将来自输入迭代器的n个T类型的数值排序,将结果通过输出迭代器result输出
template <class T, class InputIterator, class OutputIterator>
void mySort(InputIterator first, InputIterator last, OutputIterator result) {
//通过输入迭代器将输入数据存入向量容器s中
vector<T> s;
for (;first != last; ++first)
s.push_back(*first);
//对s进行排序,sort函数的参数必须是随机访问迭代器
sort(s.begin(), s.end());
copy(s.begin(), s.end(), result); //将s序列通过输出迭代器输出
}
int main() {
//将s数组的内容排序后输出
double a[5] = { 1.2, 2.4, 0.8, 3.3, 3.2 };
mySort<double>(a, a + 5, ostream_iterator<double>(cout, " "));
cout << endl;
//从标准输入读入若干个整数,将排序后的结果输出
mySort<int>(istream_iterator<int>(cin), istream_iterator<int>(), ostream_iterator<int>(cout, " "));
cout << endl;
return 0;
}
逆向迭代器的类型名的表示方式如下:
一个行为类似函数的对象
可以没有参数,也可以带有若干参数
其功能是获取一个值,或者改变操作的状态
普通函数就是函数对象
重载了“()”运算符的类的实例是函数对象
#include <iostream>
#include <numeric> //包含数值算法头文件
using namespace std;
class MultClass{ //定义MultClass类
public:
//重载操作符operator()
int operator() (int x, int y) const { return x * y; }
};
int main() {
int a[] = { 1, 2, 3, 4, 5 };
const int N = sizeof(a) / sizeof(int);
cout << "The result by multipling all elements in a is "
<< accumulate(a, a + N, 1, MultClass()) //将类multclass传递给通用算法
<< endl;
return 0;
}
#include<functional>
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main() {
int intArr[] = { 30, 90, 10, 40, 70, 50, 20, 80 };
const int N = sizeof(intArr) / sizeof(int);
vector<int> a(intArr, intArr + N);
cout << "before sorting:" << endl;
copy(a.begin(),a.end(),ostream_iterator<int>(cout,"\t"));
cout << endl;
sort(a.begin(), a.end(), greater<int>()); //STL中的二元谓词函数对象
cout << "after sorting:" << endl;
copy(a.begin(),a.end(),ostream_iterator<int>(cout,"\t"));
cout << endl;
return 0;
}
//使用width控制输出宽度
#include <iostream>
using namespace std;
int main() {
double values[] = { 1.23, 35.36, 653.7, 4358.24 };
for(int i = 0; i < 4; i++) {
cout.width(10);
cout << values[i] << endl;
}
return 0;
}
/*
输出结果:
1.23
35.36
653.7
4358.24
*/
//使用setw操纵符指定宽度
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
int main() {
double values[] = { 1.23, 35.36, 653.7, 4358.24 };
string names[] = { "Zoot", "Jimmy", "Al", "Stan" };
for (int i = 0; i < 4; i++)
cout << setw(6) << names[i]
<< setw(10) << values[i] << endl;
return 0;
}
/*
输出结果:
Zoot 1.23
Jimmy 35.36
Al 653.7
Stan 4358.24
*/
//设置对齐方式
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
int main() {
double values[] = { 1.23, 35.36, 653.7, 4358.24 };
string names[] = { "Zoot", "Jimmy", "Al", "Stan" };
for (int i=0;i<4;i++)
cout << setiosflags(ios_base::left)//左对齐
<< setw(6) << names[i]
<< resetiosflags(ios_base::left)
<< setw(10) << values[i] << endl;
return 0;
}
/*
输出结果:
Zoot 1.23
Jimmy 35.36
Al 653.7
Stan 4358.24
*/
//控制输出精度——未指定fixed或scientific
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
int main() {
double values[] = { 1.23, 35.36, 653.7, 4358.24 };
string names[] = { "Zoot", "Jimmy", "Al", "Stan" };
for (int i=0;i<4;i++)
cout << setiosflags(ios_base::left)
<< setw(6) << names[i]
<< resetiosflags(ios_base::left)//清除左对齐设置
<< setw(10) << setprecision(1) << values[i] << endl;
return 0;
}
/*
输出结果:
Zoot 1
Jimmy 4e+001
Al 7e+002
Stan 4e+003
*/
//控制输出精度——指定fixed
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
int main() {
double values[] = { 1.23, 35.36, 653.7, 4358.24 };
string names[] = { "Zoot", "Jimmy", "Al", "Stan" };
cout << setiosflags(ios_base::fixed);
for (int i=0;i<4;i++)
cout << setiosflags(ios_base::left)
<< setw(6) << names[i]
<< resetiosflags(ios_base::left)//清除左对齐设置
<< setw(10) << setprecision(1) << values[i] << endl;
return 0;
}
输出结果:
Zoot 1.2
Jimmy 35.4
Al 653.7
Stan 4358.2
//控制输出精度——指定scientific
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
int main() {
double values[] = { 1.23, 35.36, 653.7, 4358.24 };
string names[] = { "Zoot", "Jimmy", "Al", "Stan" };
cout << setiosflags(ios_base::scientific);
for (int i=0;i<4;i++)
cout << setiosflags(ios_base::left)
<< setw(6) << names[i]
<< resetiosflags(ios_base::left)//清除左对齐设置
<< setw(10) << setprecision(1) << values[i] << endl;
return 0;
}
输出结果:
Zoot 1.2e+000
Jimmy 3.5e+001
Al 6.5e+002
Stan 4.4e+003
使用ofstream构造函数中的模式参量指定二进制输出模式或以通常方式构造一个流,然后使用setmode成员函数,在文件打开后改变模式
//向二进制文件输出
#include <fstream>
using namespace std;
struct Date {
int mon, day, year;
};
int main() {
Date dt = { 6, 10, 92 };
ofstream file("date.dat", ios_base::binary);
file.write(reinterpret_cast<char *>(&dt),sizeof(dt));
file.close();
return 0;
}
将字符串作为输出流的目标,可以实现将其他数据类型转换为字符串的功能
//用ostringstream将数值转换为字符串
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
//函数模板toString可以将各种支持“<<“插入符的类型的对象转换为字符串。
template <class T>
inline string toString(const T &v) {
ostringstream os; //创建字符串输出流
os << v; //将变量v的值写入字符串流
return os.str(); //返回输出流生成的字符串
}
int main() {
string str1 = toString(5);
cout << str1 << endl;
string str2 = toString(1.2);
cout << str2 << endl;
return 0;
}
/*
输出结果:
5
1.2
*/
//get函数应用举例
#include <iostream>
using namespace std;
int main() {
char ch;
while ((ch = cin.get()) != EOF)
cout.put(ch);
return 0;
}
//为输入流指定一个终止字符
#include <iostream>
#include <string>
using namespace std;
int main() {
string line;
cout << "Type a line terminated by 't' " << endl;
getline(cin, line, 't');
cout << line << endl;
return 0;
}
//从文件读一个二进制记录到一个结构中
#include <iostream>
#include <fstream>
#include <cstring>
using namespace std;
struct SalaryInfo {
unsigned id;
double salary;
};
int main() {
SalaryInfo employee1 = { 600001, 8000 };
ofstream os("payroll", ios_base::out | ios_base::binary);
os.write(reinterpret_cast<char *>(&employee1), sizeof(employee1));
os.close();
ifstream is("payroll", ios_base::in | ios_base::binary);
if (is) {
SalaryInfo employee2;
is.read(reinterpret_cast<char *>(&employee2), sizeof(employee2));
cout << employee2.id << " " << employee2.salary << endl;
} else {
cout << "ERROR: Cannot open file 'payroll'." << endl;
}
is.close();
return 0;
}
//用seekg函数设置位置指针
int main() {
int values[] = { 3, 7, 0, 5, 4 };
ofstream os("integers", ios_base::out | ios_base::binary);
os.write(reinterpret_cast<char *>(values), sizeof(values));
os.close();
ifstream is("integers", ios_base::in | ios_base::binary);
if (is) {
is.seekg(3 * sizeof(int));
int v;
is.read(reinterpret_cast<char *>(&v), sizeof(int));
cout << "The 4th integer in the file 'integers' is " << v << endl;
} else {
cout << "ERROR: Cannot open file 'integers'." << endl;
}
return 0;
}
//读一个文件并显示出其中0元素的位置
int main() {
ifstream file("integers", ios_base::in | ios_base::binary);
if (file) {
while (file) {//读到文件尾file为0
streampos here = file.tellg();
int v;
file.read(reinterpret_cast<char *>(&v), sizeof(int));
if (file && v == 0)
cout << "Position " << here << " is 0" << endl;
}
} else {
cout << "ERROR: Cannot open file 'integers'." << endl;
}
file.close();
return 0;
}
将字符串作为文本输入流的源,可以将字符串转换为其他数据类型
//用istringstream将字符串转换为数值
template <class T>
inline T fromString(const string &str) {
istringstream is(str); //创建字符串输入流
T v;
is >> v; //从字符串输入流中读取变量v
return v; //返回变量v
}
int main() {
int v1 = fromString<int>("5");
cout << v1 << endl;
double v2 = fromString<double>("1.2");
cout << v2 << endl;
return 0;
}
/*
输出结果:
5
1.2
*/
两个重要的输入/输出流
一个函数显式声明可能抛出的异常,有利于函数的调用者为异常处理做好准备
可以在函数的声明中列出这个函数可能抛掷的所有异常类型
void fun() throw(A,B,C,D);
若无异常接口声明,则此函数可以抛掷任何类型的异常
不抛掷任何类型异常的函数声明如下:
void fun() throw();
#include <iostream>
using namespace std;
int divide(int x, int y) {
if (y == 0)
throw x;
return x / y;
}
int main() {
try {
cout << "5 / 2 = " << divide(5, 2) << endl;
cout << "8 / 0 = " << divide(8, 0) << endl;
cout << "7 / 1 = " << divide(7, 1) << endl;
} catch (int e) { //若改成float e就不会catch到
cout << e << " is divided by zero!" << endl;
}
cout << "That is ok." << endl;
return 0;
}
找到一个匹配的catch异常处理后
从最后一个catch处理之后开始恢复执行
#include <iostream>
#include <string>
using namespace std;
class MyException {
public:
MyException(const string &message) : message(message) {}
~MyException() {}
const string &getMessage() const { return message; }
private:
string message;
};
class Demo {
public:
Demo() { cout << "Constructor of Demo" << endl; }
~Demo() { cout << "Destructor of Demo" << endl; }
};
void func() throw (MyException) {
Demo d;
cout << "Throw MyException in func()" << endl;
throw MyException("exception thrown by func()");
}
int main() {
cout << "In main function" << endl;
try {
func();
} catch (MyException& e) {
cout << "Caught an exception: " << e.getMessage() << endl;
}
cout << "Resume the execution of main()" << endl;
return 0;
}
/*
运行结果:
In main function
Constructor of Demo
Throw MyException in func()
Destructor of Demo
Caught an exception: exception thrown by func()
Resume the execution of main()
*/
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。