C++ 拷贝构造函数
- 在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象
- 如果在类中没有定义拷贝构造函数,编译器会自行定义一个
- 如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数
class Line
{
public:
int getLength( void );
Line( int len ); // 简单的构造函数
Line( const Line &obj); // 拷贝构造函数
~Line(); // 析构函数
private:
int *ptr;
};
// 成员函数定义,包括构造函数
Line::Line(int len)
{
cout << "调用构造函数" << endl;
// 为指针分配内存
ptr = new int;
*ptr = len;
}
Line::Line(const Line &obj)
{
cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
ptr = new int;
*ptr = *obj.ptr; // 拷贝值
}
Line::~Line(void)
{
cout << "释放内存" << endl;
delete ptr;
}
C++ 友元函数
- 类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。
- 友元函数并不是成员函数
- 友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元
- 如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend
C++ 内联函数
- 如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方
- 对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数
- 如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline,在调用函数之前需要对函数进行定义
- 如果已定义的函数多于一行,编译器会忽略 inline 限定符
- 在类定义中的定义的函数都是内联函数,即使没有使用 inline 说明符
C++ this 指针
- C++ 中,每一个对象都能通过 this 指针来访问自己的地址
- this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象
- 友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针
C++ 指向类的指针
- 访问指向类的指针的成员,需要使用成员访问运算符 ->,就像访问指向结构的指针一样
C++ 类的静态成员
- 可以使用 static 关键字来把类成员定义为静态的
- 声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本
- 静态成员在类的所有对象中是共享的
- 创建第一个对象时,所有的静态数据都会被初始化为零
- 不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化
int Box::objectCount = 0;
静态成员函数
- 如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问
- 静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数
- 静态成员函数有一个类范围,他们不能访问类的 this 指针。您可以使用静态成员函数来判断类的某些对象是否已被创建
C++ 继承
- 继承代表了 is a 关系
- 一个类可以派生自多个类,可以从多个基类继承数据和函数
- 如果未使用访问修饰符 access-specifier,则默认为 private
- 派生类可以访问基类中所有的非私有成员
- 基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private
- 一个派生类继承了所有的基类方法,不继承 1. 基类的构造函数、析构函数和拷贝构造函数; 2. 基类的重载运算符; 3. 基类的友元函数
- 我们几乎不使用 protected 或 private 继承,通常使用 public 继承
多继承
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{
<派生类类体>
};
重载运算符
-
C++ 拷贝构造函数
在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象
如果在类中没有定义拷贝构造函数,编译器会自行定义一个
如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数
class Line
{
public:
int getLength( void );
Line( int len ); // 简单的构造函数
Line( const Line &obj); // 拷贝构造函数
~Line(); // 析构函数
private:
int *ptr;
};
// 成员函数定义,包括构造函数
Line::Line(int len)
{
cout << "调用构造函数" << endl;
// 为指针分配内存
ptr = new int;
*ptr = len;
}
Line::Line(const Line &obj)
{
cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
ptr = new int;
*ptr = *obj.ptr; // 拷贝值
}
Line::~Line(void)
{
cout << "释放内存" << endl;
delete ptr;
}
C++ 友元函数
- 类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。
- 友元函数并不是成员函数
- 友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元
- 如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend
C++ 内联函数
- 如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方
- 对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数
- 如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline,在调用函数之前需要对函数进行定义
- 如果已定义的函数多于一行,编译器会忽略 inline 限定符
- 在类定义中的定义的函数都是内联函数,即使没有使用 inline 说明符
C++ this 指针
- C++ 中,每一个对象都能通过 this 指针来访问自己的地址
- this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象
- 友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针
C++ 指向类的指针
- 访问指向类的指针的成员,需要使用成员访问运算符 ->,就像访问指向结构的指针一样
C++ 类的静态成员
- 可以使用 static 关键字来把类成员定义为静态的
- 声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本
- 静态成员在类的所有对象中是共享的
- 创建第一个对象时,所有的静态数据都会被初始化为零
- 不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化
int Box::objectCount = 0;
静态成员函数
- 如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问
- 静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数
- 静态成员函数有一个类范围,他们不能访问类的 this 指针。您可以使用静态成员函数来判断类的某些对象是否已被创建
C++ 继承
- 继承代表了 is a 关系
- 一个类可以派生自多个类,可以从多个基类继承数据和函数
- 如果未使用访问修饰符 access-specifier,则默认为 private
- 派生类可以访问基类中所有的非私有成员
- 基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private
- 一个派生类继承了所有的基类方法,不继承 1. 基类的构造函数、析构函数和拷贝构造函数; 2. 基类的重载运算符; 3. 基类的友元函数
- 我们几乎不使用 protected 或 private 继承,通常使用 public 继承
多继承
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{
<派生类类体>
};
重载运算符
- 在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载
- 重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它们的参数列表和定义(实现)不相同
- 调用一个重载函数或重载运算符时,编译器通过把您所使用的参数类型与定义中的参数类型进行比较,决定选用最合适的定义。选择最合适的重载函数或重载运算符的过程,称为重载决策
- 在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。您不能仅通过返回类型的不同来重载函数
- 重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的
C++ 多态
- 当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态
- C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数
class Shape {
int area()
{
cout << "Parent class area :" <<endl;
}
};
class Rectangle: public Shape{
int area ()
{
cout << "Rectangle class area :" <<endl;
}
};
class Triangle: public Shape{
int area ()
{
cout << "Triangle class area :" <<endl;
}
};
Shape *shape;
Rectangle rec(10,7);
Triangle tri(10,5);
// 存储矩形的地址
shape = &rec;
// 调用矩形的求面积函数 area
shape->area();
// 存储三角形的地址
shape = &tri;
// 调用三角形的求面积函数 area
shape->area();
output:
Parent class area
Parent class area
- 调用函数 area() 被编译器设置为基类中的版本,这就是所谓的静态多态,或静态链接
- 函数调用在程序执行前就准备好了。有时候这也被称为早绑定,因为 area() 函数在程序编译期间就已经设置好了
- 在 Shape 类中,area() 的声明前放置关键字 virtual
- 此时,编译器看的是指针的内容,而不是它的类型
- 有了多态,您可以有多个不同的类,都带有同一个名称但具有不同实现的函数,函数的参数甚至可以是相同的
虚函数
- 在基类中使用关键字 virtual 声明的函数
- 在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数
- 在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定
- 想要在基类中定义虚函数,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数
-
virtual int area() = 0;
= 0 告诉编译器,函数没有主体,上面的虚函数是纯虚函数
C++ 数据抽象
- 数据抽象是指,只向外界提供关键信息,并隐藏其后台的实现细节,即只表现必要的信息而不呈现细节
- 依赖于接口和实现分离的编程(设计)技术
- 在 C++ 中,我们使用访问标签来定义类的抽象接口,一个类可以包含零个或多个访问标签
- 使用公共标签定义的成员都可以访问该程序的所有部分
- 一个类型的数据抽象视图是由它的公共成员来定义的
- 使用私有标签定义的成员无法访问到使用类的代码,私有部分对使用类型的代码隐藏了实现细节
- 访问标签出现的频率没有限制,每个访问标签指定了紧随其后的成员定义的访问级别
- 指定的访问级别会一直有效,直到遇到下一个访问标签或者遇到类主体的关闭右括号为止
两个重要的优势:
- 类的内部受到保护,不会因无意的用户级错误导致对象状态受损
- 类实现可能随着时间的推移而发生变化,以便应对不断变化的需求,或者应对那些要求不改变用户级代码的错误报告
- 抽象把代码分离为接口和实现。所以在设计组件时,必须保持接口独立于实现,这样,如果改变底层实现,接口也将保持不变
C++ 数据封装
- 封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念
- 数据封装引申出了另一个重要的 OOP 概念,即数据隐藏
- 数据封装是一种把数据和操作数据的函数捆绑在一起的机制,数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制
- 通常情况下,我们都会设置类成员状态为私有(private),除非我们真的需要将其暴露,这样才能保证良好的封装性
- C++中, 虚函数可以为private, 并且可以被子类覆盖,但子类不能调用父类的private虚函数。虚函数的重载性和它声明的权限无关
- 一个成员函数被定义为private属性,标志着其只能被当前类的其他成员函数(或友元函数)所访问。而virtual修饰符则强调父类的成员函数可以在子类中被重写,因为重写之时并没有与父类发生任何的调用关系,故而重写是被允许的
- 被virtual修饰的成员函数,不论他们是private、protect或是public的,都会被统一的放置到虚函数表中
- 对父类进行派生时,子类会继承到拥有相同偏移地址的虚函数表(相同偏移地址指,各虚函数相对于VPTR指针的偏移),则子类就会被允许对这些虚函数进行重载
- 重载时可以给重载函数定义新的属性,例如public,其只标志着该重载函数在该子类中的访问属性为public,和父类的private属性没有任何关系
- 纯虚函数可以设计成私有的,不过这样不允许在本类之外的非友元函数中直接调用它
- 子类中只有覆盖这种纯虚函数的义务,却没有调用它的权利
C++ 接口(抽象类)
- 接口描述了类的行为和功能,而不需要完成类的特定实现
- C++ 接口是使用抽象类来实现的
- 如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类
- 设计抽象类的目的,是为了给其他类提供一个可以继承的适当的基类
- 抽象类不能被用于实例化对象,它只能作为接口使用,如果试图实例化一个抽象类的对象,会导致编译错误
- 如果一个 ABC 的子类需要被实例化,则必须实现每个虚函数,这也意味着 C++ 支持使用 ABC 声明接口。如果没有在派生类中重写纯虚函数,就尝试实例化该类的对象,会导致编译错误
- 面向对象的系统可能会使用一个抽象基类为所有的外部应用程序提供一个适当的、通用的、标准化的接口
- 派生类通过继承抽象基类,就把所有类似的操作都继承下来
C++ 文件和流
从文件读取流和向文件写入流。这就需要用到 C++ 中另一个标准库 fstream,它定义了三个新的数据类型
- ofstream : 输出文件流,用于创建文件并向文件写入信息
- ifstream : 输入文件流,用于从文件读取信息
- fstream : 文件流,且同时具有 ofstream 和 ifstream 两种功能,这意味着它可以创建文件,向文件写入信息,从文件读取信息
在 C++ 中进行文件处理,必须在 C++ 源代码文件中包含头文件 <iostream> 和 <fstream>
打开文件
- 从文件读取信息或者向文件写入信息之前,必须先打开文件
- ofstream 和 fstream 对象都可以用来打开文件进行写操作,如果只需要打开文件进行读操作,则使用 ifstream 对象
- open() 成员函数的第一参数指定要打开的文件的名称和位置,第二个参数定义文件被打开的模式
- ios::app 追加模式,所有写入都追加到文件末尾
- ios::ate 文件打开后定位到文件末尾
- ios::in 打开文件用于读取
- ios::out 打开文件用于写入
- ios::trunc 如果该文件已经存在,其内容将在打开文件之前被截断,即把文件长度设为 0
- 可以把以上两种或两种以上的模式结合使用,
ios::out | ios::trunc
ios::out | ios::in
- C++ 程序终止时,它会自动关闭刷新所有流,释放所有分配的内存,并关闭所有打开的文件
- 我们使用流插入运算符( << )向文件写入信息,就像使用该运算符输出信息到屏幕上一样。唯一不同的是,在这里您使用的是 ofstream 或 fstream 对象,而不是 cout 对象
- 我们使用流提取运算符( >> )从文件读取信息,就像使用该运算符从键盘输入信息一样。唯一不同的是,在这里您使用的是 ifstream 或 fstream 对象,而不是 cin 对象
- cin.getline()函数从外部读取一行
- cin.ignore() 函数会忽略掉之前读语句留下的多余字符
- 关于 istream 的 seekg("seek get")和关于 ostream 的 seekp("seek put")
- 参数通常是一个长整型。第二个参数可以用于指定查找方向。查找方向可以是 ios::beg(默认的,从流的开头开始定位),也可以是 ios::cur(从流的当前位置开始定位),也可以是 ios::end(从流的末尾开始定位)
- 文件位置指针是一个整数值,指定了从文件的起始位置到指针所在位置的字节数
C++ 异常处理
- 异常提供了一种转移程序控制权的方式
- C++ 异常处理涉及到三个关键字:try、catch、throw