C++类的基础知识总结
C++中的类
概念
类用于指定对象的形式,它包含了数据表示法和用于处理数据的方法。类中的数据和方法称为类的成员。函数在一个类中被称为类的成员。
类的实质是一种引用数据类型。每个类包含数据说明和一组操作数据或传递消息的函数。类的实例称为对象。
特性
封装性
将数据和操作封装为一个有机的整体,由于类中私有成员都是隐藏的,只向外部提供有限的接口,所以能够保证内部的高内聚性和与外部的低耦合性。用者不必了解具体的实现细节,而只是要通过外部接口,以特定 的访问权限来使用类的成员,能够增强安全性和简化编程。
继承性
子类可以继承父类的属性和方法,不用再写一遍。
多态性
同一操作作用于不同对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向父类(基类)的指针,来调用实现子类(派生类)中的方法。
C++中类的定义
class classname{
Access specifiers: //private pubilic protected
Data members/variables;
Member functions(){}
};
访问类中的\(Member\)时,直接classname.membername
,但\(private\)和\(protected\)的\(Member\)不能用.
来访问。
类成员函数
如同其他\(Member\)一样,类中函数也是一种\(Member\)。
成员函数可以定义在类定义内部,或者单独使用范围解析运算符 :: 来定义。
注:在\(::\)运算符之前必须使用类名。
类访问修饰符
就是上面提到的 \(public~private~protected\)。
一个类可以有多个 $public、~protected $ 或 \(private\) 标记区域。每个标记区域在下一个标记区域开始之前或者在遇 到类主体结束右括号之前都是有效的。成员和类的默认访问修饰符是 \(private\)。
public
\(pubilcmember\) 在程序中类的外部是可访问的,即是说我们可以直接在外部修改 \(publicmember\) ,比如:
class classname{
...
};
int main(){
classname.membername = value;
return 0;
}
private
顾名思义,对比前面的 \(public\) ,其实意思就是 \(privatemember\)在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。
注:默认情况下类的所有成员都是 \(private\) 。
实际操作中,我们一般会在私有区域定义数据,在公有区域定义相关的函数,以便在类的外部也可以调用这些函数。
protected
和 \(private\) 差不多,区别在于 \(protectedmember\) 在子类中是可访问的。
类的构造函数与析构函数
用于初始化 \(classmember\) ,其在每次创建类的新对象时执行。构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 \(void\) 。
这是一个构造函数的例子:
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
class PC {
public:
void setprice(double x);
double getprice();
PC();
private:
double price;
};
//这个是构造函数
PC::PC(void) {
cout << "PC has been generated" << endl;
}
void PC::setprice(double x) {
price = x;
}
double PC::getprice() {
return price;
}
int main()
{
double price;
PC ALIENWARE;
cout << "Enter Price: " << endl;
cin >> price;
ALIENWARE.setprice(price);
cout << "Price is: " << ALIENWARE.getprice() << endl;
return 0;
}
其中PC()
即为构造函数,定义方式如代码所示。
带参数的构造函数
用于初始化,下面是例子:
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
class PC {
public:
void setprice(double x);
double getprice();
PC(double prc);
private:
double price;
};
//这个是构造函数
PC::PC(double prc) {
cout << "PC has been generated" << endl;
price = prc;
cout << "Defult price is: " << prc << endl;
}
void PC::setprice(double x) {
price = x;
}
double PC::getprice() {
return price;
}
int main()
{
double price;
PC ALIENWARE(0.0);
cout << "Enter Price: " << endl;
cin >> price;
ALIENWARE.setprice(price);
cout << "Price is: " << ALIENWARE.getprice() << endl;
return 0;
}
初始化列表
class A{
public:
double X,Y,Z;
};
A::A(double a, double b, double c) : X(a), Y(b), Z(c)
类的析构函数
与构造函数相反,析构函数在每次删除对象时执行,有助于在跳出程序前释放资源。
注:析构函数不返回任何值,也不能带参数。
拿上面的代码改下做例子:
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
class PC {
public:
void setprice(double x);
double getprice();
PC(double prc);
~PC();
private:
double price;
};
PC::PC(double prc) {
cout << "PC has been generated" << endl;
price = prc;
cout << "Defult price is: " << prc << endl;
}
//这个就是析构函数
PC::~PC() {
cout << "PC sold" << endl;
}
void PC::setprice(double x) {
price = x;
}
double PC::getprice() {
return price;
}
int main()
{
double price;
PC ALIENWARE(0.0);
cout << "Enter Price: " << endl;
cin >> price;
ALIENWARE.setprice(price);
cout << "Price is: " << ALIENWARE.getprice() << endl;
return 0;
}
注:1、一个类内可以有多个构造函数,但只能有一个析构函数。
2、C++初始化 \(classmember\) 时,是按照声明顺序初始化的,而不是按照出现在初始化列表中的顺序。
拷贝构造函数
拷贝构造函数是一种特殊的构造函数,具有单个形参,该形参(常用const修饰)是对该类类型的引用。当定义一个新对象并用一个同类型的对象对它进行初始化时,将显示使用拷贝构造函数。当该类型的对象传递给函数或从函数返回该类型的对象时,将隐式调用拷贝构造函数。
必须定义拷贝构造函数的情况:
只包含类类型成员或内置类型(但不是指针类型)成员的类,无须显式地定义拷贝构造函数也可以拷贝;有的类有一个数据成员是指针,或者是有成员表示在构造函数中分配的其他资源,这两种情况下都必须定义拷贝构造函数。
C++调用拷贝构造函数的情况:
1、一个对象以值传递的方式传入函数体
2、一个对象一值传递的额方式从函数返回
3、一个对象需要通过另外一个对象进行初始化
注:当出现类的等号赋值时,会调用拷贝函数,在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝,它能够完成成员的一一复制。当数据成员中没有指针时,浅拷贝是可行的。但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象(指针指向非法的内存地址,那么这个指针就是悬挂指针,也叫野指针)。所以,这时,必须采用深拷贝。
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
class PC {
public:
void setprice(double x);
double getprice();
PC(double prc);
PC(const PC& obj);
~PC();
private:
double* price;
};
//这个是拷贝构造函数
PC::PC(const PC& obj) {
cout << "new class copied." << endl;
price = new double;
*price = *obj.price;
}
PC::PC(double prc) {
cout << "PC has been gernerated" << endl;
price = new double;
*price = prc;
cout << "Defult price is: " << prc << endl;
}
PC::~PC() {
cout << "PC sold" << endl;
delete price;
}
void PC::setprice(double x) {
*price = x;
}
double PC::getprice() {
return *price;
}
int main()
{
double price;
PC ALIENWARE(0.0);
cout << "Enter Price: " << endl;
cin >> price;
ALIENWARE.setprice(price);
PC ASUS = ALIENWARE;
cout << "ASUS price : " << ASUS.getprice() << " ALIENWARE price : " << ALIENWARE.getprice() <<endl;
return 0;
}
友元函数
类的友元函数是定义在类外部,但有权访问类的所有 \(private\) 成员和 \(protected\) 成员的函数。
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
友元函数的声明
若要声明函数为一个类的友元,则在类定义中在该函数前加上friend
。若要声明一个类 \(A\) 的所有成员为另一个类 \(B\) 的友元,则需在 \(B\) 中添加friend class A
举个例子:
C#include<cstdio>
#include<iostream>
using namespace std;
class PC {
public:
friend void printprice(PC PC_0)//友元函数
void setprice(double prc);
private:
double price;
};
void PC::setprice(double prc) {
price = prc;
cout << "price set as :" << prc << endl;
}
void printprice(PC PC_0) {
cout << "price is: " << PC_0.price << endl;//友元函数直接访问PC的classmember
}
int main()
{
PC ROG;
double price;
cout << "Enter price: " << endl;
cin >> price;
ROG.setprice(price);
printprice(ROG);
return 0;
}
友元类的声明
把上面的代码改一下:
#include<cstdio>
#include<iostream>
using namespace std;
class PC {
public:
void setprice(double prc);
friend class Print;
private:
double price;
};
class Print {
public:
void printprice(PC PC_0);
};
void Print::printprice(PC PC_0) {
cout << "Price is: " << PC_0.price;
}
void PC::setprice(double prc) {
price = prc;
cout << "price set as :" << prc << endl;
}
int main()
{
PC ROG;
Print printer;
double price;
cout << "Enter price: " << endl;
cin >> price;
ROG.setprice(price);
printer.printprice(ROG);
return 0;
}
内联函数
其实就是在函数声明前面加个inline
。
引入内联函数的目的是为了解决程序中函数调用的效率问题,程序在编译器编译的时候,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体进行替换,而对于其他的函数,都是在运行时候才被替代。
注:1、内联函数中不允许使用循环和\(switch\)。
2、内联函数的定义必须出现在内联函数第一次调用之前。
this指针
C++中每个对象都能通过 \(this\) 指针来访问自己的地址。
#include<cstdio>
#include<iostream>
#include<typeinfo>
using namespace std;
class PC {
public:
PC(double prc) {
price = prc;
}
bool max(PC PC_0) {
return (this->price >= PC_0.price) ? true : false;
}
private:
double price;
};
int main()
{
PC ALIENWARE(15999), ASUS(12999);
cout << "The expensive one is: " << endl;
if (ALIENWARE.max(ASUS)) cout << "ALIENWARE" << endl;
else cout << "ASUS" << endl;
return 0;
}
注:友元函数没有 \(this\) 指针,只有成员函数才有。
静态成员
其实就是在 \(classmember\) 声明前面加个static
。
通俗的说静态成员是一个类的所有对象的共享成员。
举个例子,下面的代码实现了统计类的对象的个数的功能:
#include<cstdio>
#include<iostream>
#include<typeinfo>
using namespace std;
class PC {
public:
PC(double prc) {
price = prc;
num++;
}
static int num;//静态成员数据
static int getnum();//静态成员函数
private:
double price;
};
int PC::num = 0;
int PC::getnum()
{
return PC::num;
}
int main()
{
PC ALIENWARE(15999), ASUS(12999);
cout << "total PC :" << PC::getnum() << endl;
return 0;
}
注:1、静态成员函数即使在类对象不存在的情况下也能被调用。
2、静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。
3、静态成员函数没有 \(this\) 指针。
4、静态成员变量在类中仅仅是声明,没有定义,所以要在类的外面定义,实际上是给静态成员变量分配内 存。如果不加定义就会报错,初始化是赋一个初始值,而定义是分配内存。
C++中的继承
在C++中,继承允许我们依据另一个类来定义一个类。创建一个新类时,只需指定新类继承了一个已有的类的成员即可,大大简化了代码结构。继承可以比作集合之间的包含关系,一个父类即是其子类的子集。
注:子类不继承父类的构造函数、析构函数和拷贝构造函数,重载运算符,友元函数。
子类和父类(基类和派生类)
定义子类所需格式:
class derived_class : access_specifier base_class \\derived_class子类 base_class父类
注:一个类可以派生自多个类。其中 \(access\_specifier\) 可以是 \(public~protected~private\) 中任意一个,若未制订则默认为 \(private\)。
一个例子:
#include<cstdio>
#include<iostream>
using namespace std;
class PC {
public:
void setprice(double prc);
double getprice();
private:
double price;
};//父类
void PC::setprice(double prc) {
price = prc;
}
double PC::getprice() {
return price;
}
class ALIENWARE : public PC {
public:
ALIENWARE();
};//子类
ALIENWARE::ALIENWARE() {
cout << "ALIENWARE has been generated." << endl;
}
int main()
{
ALIENWARE m15r4;
m15r4.setprice(15999);
cout << "Price is: " << m15r4.getprice() << endl;
return 0;
}
注:1、子类不可访问父类的 \(private\) 成员,但可以通过调用父类的 \(public\) 或 \(protected\) 成员来访问。
2、继承类型一般使用 \(public\) ,这样子类继承的成员的类型都与父类一致(即父类 \(public\) 成员在子类也是 \(public\) , \(protected~private\) 同理)。
子类中的构造函数
上面提到,子类不能直接访问父类的 \(private\) 成员,而必须通过调用父类的 \(public/protected\) 方法来访问,所以子类的构造函数是不能直接设置其从父类继承的成员的。因此,子类构造函数必须使用父类的构造函数。
下面给俩例子,分别是不同的子类构造函数实现方式:
#include<cstdio>
#include<iostream>
using namespace std;
class PC {
public:
void setprice(double prc);
double getprice();
private:
double price;
};
void PC::setprice(double prc) {
price = prc;
}
double PC::getprice() {
return price;
}
class ALIENWARE : public PC {
public:
double weight;
ALIENWARE(double wgt, const PC& PC_0);
};
ALIENWARE::ALIENWARE(double wgt, const PC& PC_0) : weight(wgt), PC(PC_0)
{
cout << "ALIENWARE has been generated." << endl;
}
//这里有个小细节要注意下,这个构造函数的参数里面父类的形参必需带个引用&。原因是使用引用就不用初始化父类的对象A了,如果不带&的话必须先初始化A。为了更好地展示这一点,这部分代码就没写父类的构造函数。
//**在函数调用过程中,如果参数不是引用调用,那就必须要对其进行初始化,如果是引用调用,就没有影响。**
int main()
{
PC A;
//cout << "A's price is: " << A.price << endl;
//注意到如果没有自己写构造函数,而是使用编译器默认构造函数的话,这个地方A是没有初始化的,所以会导致 //上面注释里提到的情况。
//总之编译器默认构造函数非常坑,建议都自己写。
ALIENWARE m15r4(4,A);
m15r4.setprice(15999);
cout << "Price is: " << m15r4.getprice() << endl;
cout << "Wight is: " << m15r4.weight << endl;
return 0;
}
PS:如有意获取更多有关默认构造函数的内容,参见https://blog.csdn.net/bear_n/article/details/72798301。
#include<cstdio>
#include<iostream>
using namespace std;
class PC {
public:
void setprice(double prc);
double getprice();
PC(double prc);
private:
double price;
};
//第二种实现方式就必须把父类的构造函数写出来传参数
PC::PC(double prc) {
price = prc;
}
void PC::setprice(double prc) {
price = prc;
}
double PC::getprice() {
return price;
}
class ALIENWARE : public PC {
public:
double weight;
ALIENWARE(double wgt, double prc);
};
ALIENWARE::ALIENWARE(double wgt, double prc) : weight(wgt), PC(prc)
{
cout << "ALIENWARE has been generated." << endl;
}
int main()
{
ALIENWARE m15r4(4, 15999);
cout << "Price is: " << m15r4.getprice() << endl;
cout << "Wight is: " << m15r4.weight << endl;
return 0;
}
其实还有一种,含有类对象作为数据成员的类的构造函数。但是这个东西跟子类的构造函数比较相似,就不展开了。
简单提一下“含有类对象作为数据成员的类”是个什么玩意:
class A {
...
};
class B {
public:
A a;
...
};
//这个B就是“含有类对象作为数据成员的类”。
多继承
C++允许一个子类继承自多个父类:
class derived_class : access_specifier base_class_1, access_specifier base_class_2 ...{
...
};
多态
由于C++编译器遇到子类与父类具有同名但不同功能的函数时,默认调用的是父类中的版本(静态链接),造成了诸多不便,C++便提供了多态来扩展程序的可用性。
C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
形成多态必须具备三个条件:
1、必须存在继承关系;
2、继承关系必须有同名虚函数(其中虚函数是在父类中使用关键字virtual
声明的函数,在子类中重新定 义父类中定义的虚函数时,会告诉编译器不要静态链接到该函数);
3、存在父类类型的指针或者引用,通过该指针或引用调用虚函数;
虚函数与纯虚函数
虚函数是在父类中用关键字virtual
声明的函数。在子类中重新定义父类中的虚函数后,使用特定对象类型调用该函数时,编译器会动态链接到所需的函数。
纯虚函数就是一个真的什么都不干的虚函数,格式如下:
virtual typename function() = 0;
纯虚函数的作用大概就是节约时间,反正虚函数一般不会被调用。。。
一个例子:
#include<cstdio>
#include<iostream>
using namespace std;
class PC {
public:
void setprice(double prc);
double getprice();
virtual void PRICE() = 0;//用纯虚函数就可以偷懒力
PC();
private:
double price;
};//父类
PC::PC() : price(0) {}
double PC::getprice() {
return price;
}
void PC::setprice(double prc) {
price = prc;
}
class ALIENWARE : public PC {
public:
ALIENWARE();
void PRICE() {
cout << "Price of ALIENWARE is: " << this->getprice() << endl;
}
};
class ASUS : public PC {
public:
ASUS();
void PRICE() {
cout << "Price of ASUS is: " << this->getprice() << endl;
}
};
ALIENWARE::ALIENWARE() {
cout << "ALIENWARE has been generated." << endl;
}
ASUS::ASUS() {
cout << "ASUS has been generated." << endl;
}
int main()
{
ALIENWARE m15r4;
ASUS ROG;
m15r4.setprice(15999);
ROG.setprice(14999);
m15r4.PRICE();
ROG.PRICE();
return 0;
}
注:1、含有纯虚函数的类叫抽象类,抽象类不能创建对象·定义实例,但可以声明指向实现该抽象类的具体类的指 针或引用。
2、在有动态分配堆上内存的时候,析构函数必须是虚函数,但没有必要是纯虚的。
3、友元不是成员函数,只有成员函数才可以是虚拟的,因此友元不能是虚拟函数。但可以通过让友元函数调 用虚拟成员函数来解决友元的虚拟问题。
C++重载运算符和重载函数
C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载。也就是说,允许出现多个同名不同功能的函数或运算符。
重载函数
这个没什么好说的:
#include<cstdio>
#include<iostream>
using namespace std;
void print(int x) {
cout << "integer: " << x << endl;
}
void print(double x) {
cout << "double float: " << x << endl;
}
void print(char x[]) {
cout << "string: " << x << endl;
}
int main()
{
int a = 1; double b = 3.0; char c[] = "Hello";
print(a);
print(b);
print(c);
return 0;
}
注:重载函数时,编译器不区分类型的引用和类型本身。
重载运算符
这个就比较重要了,但这里不详细写。
C++中大部分内置运算符支持重载,这个重载相当于自定义运算。实际上,重载运算符是一种特殊的函数,因此带有返回类型和参数列表。它的形式如下:
返回值类型 operater要重载的运算符(参数列表);
重载运算符在类里面非常好用,它使得类的对象间的运算成为可能:
#include<cstdio>
#include<iostream>
using namespace std;
class Junior_high_3 {
public:
void setnum(int mal, int fel);
int getnum();
Junior_high_3();
Junior_high_3 operator+(const Junior_high_3& cls) {
Junior_high_3 tot;
tot.male = this->male + cls.male;
tot.female = this->female + cls.female;
return tot;
}
private:
int male, female;
};
Junior_high_3::Junior_high_3() : male(0), female(0) {}
int Junior_high_3::getnum() {
return male + female;
}
void Junior_high_3::setnum(int mal, int fel) {
male = mal, female = fel;
}
int main()
{
Junior_high_3 class1, class2, grade;
int m1, f1, m2, f2;
cout << "Enter the num of boys in class1:" << endl;
cin >> m1;
cout << "Enter the num of girls in class1:" << endl;
cin >> f1;
cout << "Enter the num of boys in class2:" << endl;
cin >> m2;
cout << "Enter the num of girls in class2:" << endl;
cin >> f2;
class1.setnum(m1, f1), class2.setnum(m2, f2);
grade = class1 + class2;
cout << "Our grade has " << grade.getnum() << " students" << endl;
return 0;
}
其他运算符的重载跟这个基本一样,但有很多细节之处需要注意。
注:不可重载的运算符有. .* ->* :: sizeof ?: #