定义一个类,本质上是定义一个数据类型的蓝图,它定义了类的对象包括了什么,以及可以在这个对象上执行哪些操作。
class girl_friend
{
public:
int age;
string name;
void set_name(string n)
{
name = n;
}
void set_age(int a)
{
age = a;
}
};
对象是根据类来创建
girl_friend gf1;
girl_friend gf2;
访问数据成员
public的成员可以直接使用” . “访问
gf1.age
访问权限 | public | protected | private |
---|---|---|---|
同一个类 | 有 | 有 | 有 |
派生类 | 有 | 有 | 无 |
外部类 | 有 | 无 | 无 |
每个对象都有一个this指针,this指针指向当前对象
当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象的this指针作为一个隐含参数传递给函数
class girl_friend
{
public:
int age;
string name;
void fun()
{
cout << this->age << endl;
//cout << age << endl;
cout << (*this).name << endl;
//cout << name << endl;
}
girl_friend fun1()
{
return *this;//返回对象拷贝
}
girl_friend &fun2()
{
return *this;//返回对象引用
}
girl_friend* fun3()
{
return this;//返回对象指针
}
};
使用static可以把类成员定义为静态的
static修饰的类成员属于类,不属于对象
static成员变量是所有对象共享的变量,先于对象存在,所以必须在类外进行初始化
class girl_friend
{
public:
static int age;
string name;
};
int girl_friend::age = 0;
int main()
{
girl_friend gf1;
cout << gf1.age;//0
gf1.age = 1;
girl_friend gf2;
cout << gf2.age;//1
girl_friend::age = 2;
cout << gf1.age;//2
}
static成员变量在静态储存区生成,不与对象一起生成,在对象中不占内存,可以节省对象的内存空间
static类成员属于类,不属于对象,所以static类成员函数没有this指针,所以static类成员函数不能访问非static的类成员,只能访问 static类成员。
class girl_friend
{
public:
static int age;
string name;
static void fun()
{
cout << age;
//cout << name;报错
}
};
int girl_friend::age = 0;
类的友元函数定义在类外部,但有权访问类的所有成员。
友元函数的原型在类的定义中出现过,但是友元函数并不是成员函数。
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,友元类的所有成员函数都是友元函数。
声明友元使用关键字friend
#include<iostream>
#include<string>
using namespace std;
class girl_friend
{
public:
int age;
void set_name(string n)
{
name = n;
}
friend void get_name(girl_friend gf);//友元函数
friend class Get_name;//友元类
private:
string name;
};
void get_name(girl_friend gf)
{
cout << gf.name;
}
class Get_name
{
public:
void get_name(girl_friend gf)
{
cout << gf.name;
}
};
int main()
{
girl_friend gf1;
Get_name g;
gf1.set_name("123");
g.get_name(gf1);
get_name(gf1);
}
类通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数就是构造函数
构造函数的名字与类名相同,有一个参数列表和一个函数体,无返回类型。
类中没有显式的定义构造函数,编译器就会隐式的定义一个默认构造函数
默认构造函数无实参
初始化规则:如果存在类内初始值,用它初始化成员,否则默认初始化成员。
类中可以有多个构造函数,如果既需要其他形式的构造函数,也需要默认的构造函数,就需要定义一个默认构造函数
class girl_friend
{
public:
girl_friend()=default;//默认构造函数
string name;
};
class girl_friend
{
public:
girl_friend(string &n,int &a):name(n),age(a)
{
cout << "Girl_friend is being created" << endl;
}
string name;
int age;
int height;
};
其中冒号后为构造函数初始值列表,每个成员名字后的括号内是成员初始值。
其中height
没有显式初始化,按照默认构造函数的方式隐式初始化。
也可以在构造函数内赋值
class girl_friend
{
public:
girl_friend(string &n,int &a)
{
name = n;
age = a;
cout << "Girl_friend is being created" << endl;
}
string name;
int age;
int height;
};
如果没有在构造函数的初始值列表中显式的初始化成员,该成员将在构造函数体之前默认初始化。
类成员初始化总在构造函数执行之前,这会导致在构造函数内赋值出现问题。
如果成员是const就无法赋值,而引用类型必须在初始化时必须有初值,所以对于const和引用类型在构造函数内赋值会出现错误,应该使用冒号初始化。
拷贝构造函数在创建对象时使用同一类中之前创建的对象来初始化新的对象。
如果没有显式定义拷贝构造函数,编译器就会隐式的定义一个默认拷贝构造函数。
class girl_friend
{
public:
girl_friend(const girl_friend &gf1)=default;
};
girl_friend gf1;//直接初始化,调用默认构造函数
girl_friend gf2 = gf1;//拷贝初始化,调用默认拷贝构造函数
冒号初始化
class girl_friend
{
public:
girl_friend(girl_friend &gf1):name(gf1.name),age(gf1.age)
{
cout << "Girl_friend is being created" << endl;
}
string name;
int age;
};
函数体内赋值
class girl_friend
{
public:
girl_friend(girl_friend &gf1)
{
cout << "Girl_friend is being created" << endl;
name = gf1.name;
age = gf1.age;
}
string name;
int age;
};
girl_friend gf3(gf1)//拷贝初始化,调用拷贝构造函数
class girl_friend
{
public:
girl_friend(string &n,int &a);
string name;
int age;
int height;
};
girl_friend::girl_friend(string &n,int &a):name(n),age(a)
{
cout << "Girl_friend is being created" << endl;
}
在类中声明,在外部定义
析构函数删除所创建的对象
析构函数名字与类名相同,前面加~
,没有参数和返回值
如果没有显式定义析构函数,编译器就会隐式的定义一个默认析构函数
class girl_friend
{
public:
girl_friend()
{
cout << "Girl_friend is being created" << endl;
}
~girl_friend()
{
cout << "Girl_friend is being destroyed" << endl;
}
};
在回收用 new 分配的单个对象的内存空间的时候用 delete,回收用 new 分配的一组对象的内存空间的时候用 delete[]。
new 会调用构造函数,delete会调用析构函数,delete[] 调用每个数组元素的析构函数。
class girl_friend
{
public:
girl_friend()
{
cout << "Girl_friend is being created" << endl;
}
~girl_friend()
{
cout << "Girl_friend is being destroyed" << endl;
}
};
int main()
{
girl_friend* gf = new girl_friend[2];
delete[] gf;
cout << "==========" << endl;
girl_friend* gf1 = new girl_friend;
delete gf1;
//delete[] gf1;错误
}
/*
Girl_friend is being created
Girl_friend is being created
Girl_friend is being destroyed
Girl_friend is being destroyed
==========
Girl_friend is being created
Girl_friend is being destroyed
*/
创建一个类时,可以不重新编写新的数据成员和成员函数,指定新建的类继承了一个已有的类的成员。
继承代表 is a 关系
已有的类称为基类,新建的类称为派生类,一个类可以派生自多个类,它可以从多个基类继承数据和函数。
A派生B,B派生C,A是B的直接基类,B是C的直接基类,A是C的间接基类,
声明派生类时,使用类派生列表来指定基类,只需要列出直接基类,派生类自动向上继承间接基类
class people
{
public:
int age;
string name;
};
class find_girlfriend
{
public:
void find()
{
cout << "finding..." << endl;
cout << "you cann't find girlfriend" << endl;
}
};
class damn_single : public people, public find_girlfriend
{
public:
void single()
{
cout << "you don't have girlfriend" << endl;
}
};//damn_single是people和find_girlfriend的派生类
一个派生类继承了所有的基类方法,但下列情况除外:
派生类对象包含基类对象,基类对象的储存位置位于派生类对象新增的成员之前
公有继承(public):基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
保护继承(protected): 基类的公有和保护成员将成为派生类的保护成员。
私有继承(private):基类的公有和保护成员将成为派生类的私有成员。
同名成员
派生类与基类存在同名成员时,基类成员会被隐藏,派生类中存在基类成员的拷贝,可以通过base::name访问
class people
{
public:
int age;
char* name;
};
class find_girlfriend
{
public:
void single()
{
cout << "Use 'find()' to find girlfriend" << endl;
}
void find()
{
cout << "you cann't find girlfriend" << endl;
}
int years = 18;
};
class damn_single : public people, public find_girlfriend
{
public:
void single()
{
cout << "you don't have girlfriend" << endl;
}
int years = 19;
};
int main()
{
damn_single ds;
ds.find_girlfriend::single();
ds.single();
cout << ds.find_girlfriend::years << endl;
cout << ds.years << endl;
}
/*
Use 'find()' to find girlfriend
you don't have girlfriend
18
19
*/
创建派生类对象时需要调用基类的构造函数初始化从基类继承的成员
在执行派生类的构造函数前先执行基类的构造函数
class people
{
public:
people(int a) :age(a)
{
cout << "people is being created" << endl;
}
int age;
};
class girl_friend : public people
{
public:
girl_friend(int a, bool b) :people(a), age(a), isGirl(b)
{
cout << "Girl_friend is being created" << endl;
}
int age;
bool isGirl;
};
int main()
{
girl_friend gf(1, true);
}
/*
people is being created
Girl_friend is being created
*/
派生类的构造函数为基类的构造函数提供参数
派生类的构造函数省略基类构造函数时默认调用基类的默认构造函数
派生类的析构函数执行后,自动调用基类的析构函数
class people
{
public:
people(int a) :age(a)
{
cout << "people is being created" << endl;
}
~people()
{
cout << "People is being destroyed" << endl;
}
int age;
};
class girl_friend : public people
{
public:
girl_friend(int a, bool b) :people(a), age(a), isGirl(b)
{
cout << "Girl_friend is being created" << endl;
}
~girl_friend()
{
cout << "Girl_friend is being destroyed" << endl;
}
int age;
bool isGirl;
};
int main()
{
girl_friend gf(1, true);
gf.~girl_friend();
}
/*
people is being created
Girl_friend is being created
Girl_friend is being destroyed
People is being destroyed
*/
C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载。
重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它们的参数列表和定义不相同。
调用一个重载函数或重载运算符时,编译器通过把使用的参数类型与定义中的参数类型进行比较,选用最合适的定义,这个过程称为重载决策。
在同一个作用域内,可以声明几个同名函数,但这些同名函数的形参(个数、类型或顺序)必须不同
不能仅通过返回类型的不同来重载函数。
class find_girlfriend
{
public:
void find()
{
cout << "You cann't find girlfriend" << endl;
}
void find(int n)
{
cout << "Y0u c4nN't f1nd Girlfri3nd" << endl;
}
void find(int a, int b)
{
cout << "Y0u c4Nnnnnnnnnnnnnnnn't f1nd Girlfri3nd" << endl;
}
/*
int find(int n)
{
return n;
}
报错
*/
};
int main()
{
find_girlfriend f;
f.find();
f.find(1);
f.find(1, 1);
}
/*
You cann't find girlfriend
Y0u c4nN't f1nd Girlfri3nd
Y0u c4Nnnnnnnnnnnnnnnn't f1nd Girlfri3nd
*/
运算符重载的实质是函数重载
重载的运算符是有特殊名称的函数,由关键字 operator 和要重载的运算符构成,有一个返回类型和参数列表
运算符重载不改变运算符的优先级
class find_girlfriend
{
public:
int fail_counter = 1;
int operator+(find_girlfriend f)//重载为成员函数
{
return this->fail_counter + f.fail_counter;//this为左操作数
}
int operator+(int n)//重载为成员函数
{
return this->fail_counter + n;//this为左操作数
}
friend int operator *(find_girlfriend a, int n);//重载为友元函数
private:
int success_counter = 0;
};
int operator -(find_girlfriend a, find_girlfriend b)//重载为普通函数
{
return a.fail_counter - b.fail_counter;
}
int operator *(find_girlfriend a, int n )
{
return a.success_counter * n;
}
int main()
{
find_girlfriend f1;
find_girlfriend f2;
cout << f1 + f2 << endl;//2
cout << f1 + 2 << endl;//3
cout << f1 - f2 << endl;//0
cout << f1 * 1 << endl;//0
}
运算符可以作为成员函数重载,作为友元函数重载,作为普通函数重载
作为友元函数重载可以在外部定义,但必须在类内部声明。
单目运算符作为类成员函数重载时没有型参(后置自增(自减)有一个整型参数)
双目运算符作为类成员函数重载时只有一个型参,作为运算符的右操作数。左操作数就是当前对象(this)。
单目运算符一般重载为成员函数,
可重载运算符 | 可重载运算符 |
---|---|
双目算术运算符 | 双目算术运算符 + (加),-(减),*(乘),/(除),% (取模) |
关系运算符 | ==(等于),!= (不等于),< (小于),> (大于>,<=(小于等于),>=(大于等于) |
逻辑运算符 | ||(逻辑或),&&(逻辑与),!(逻辑非) |
单目运算符 | + (正),-(负),*(指针),&(取地址) |
自增自减运算符 | ++(自增),–(自减) |
位运算符 | | (按位或),& (按位与),~(按位取反),^(按位异或),,« (左移),»(右移) |
赋值运算符 | =, +=, -=, *=, /= , % = , &=, |=, ^=, «=, »= |
空间申请与释放 | new, delete, new[ ] , delete[] |
其他运算符 | ()(函数调用),->(成员访问),,(逗号),[] (下标) |
赋值运算符“=”、下标运算符“[ ]”、函数调用运算符”( )”、成员运算符“->”必须作为成员函数重载。
不可重载运算符 |
---|
成员访问运算符 . |
成员指针访问运算符 .* , ->* |
域运算符 :: |
长度运算符- sizeof |
条件运算符 ?: |
预处理符号 # |
(5,5)++ == (5, 5)
++(5,5) == (6,6)
(5,5) & (6,6) == (4,4)
(5,5) >> 2 == (1,1)
#include<iostream>
using namespace std;
class point
{
public:
int x = 0;
int y = 0;
void show_point()
{
cout << "(" << x << "," << y << ")" << endl;
}
point operator ++()
{
++x;
++y;
return *this;
}
point operator ++(int)
{
return *this;
}
point operator >> (int n)
{
x = x >>n;
y = y >>n;
return *this;
}
point operator &(point p)
{
x = x & p.x;
y = y & p.y;
return *this;
}
};
int main()
{
point a, b, c;
int o, n;
cout << "输入a坐标" << endl;
cin >> a.x >> a.y;
cout << "1:a++;2:++a;3:a&b;4:a>>n" << endl;
cin >> o;
switch (o)
{
case 1:
c = a++;
c.show_point();
break;
case 2:
c = ++a;
c.show_point();
break;
case 3:
cout << "输入b坐标" << endl;
cin >> b.x >> b.y;
c = a & b;
c.show_point();
break;
case 4:
cout << "输入n" << endl;
cin >> n;
c = a >> n;
c.show_point();
break;
default:
cout << "Error" << endl;
}
}
接口的多种不同的实现方式即为多态
静态多态在编译期间完成,编译器通过把使用的参数类型与定义中的参数类型进行比较,选用最合适的定义(重载决策)
运行时的多态,在程序执行期间(非编译期)判断所引用对象的实际类型,选用最合适的定义。
动态多态通过虚函数实现
虚函数是在基类中使用关键字 virtual 声明的函数
派生类的指针可以赋给基类指针,通过基类指针调用派生类的同名虚函数时,根据该指针的动态类型调用函数
派生类的对象可以赋给基类引用,通过基类引用调用派生类的同名虚函数时,根据该引用的动态类型调用函数
#include<iostream>
#include<string>
using namespace std;
class find_friend
{
public:
virtual void find()//virtual声明虚函数
{
cout << "you cann't find friend" << endl;
}
};
class find_girlfriend : public find_friend
{
public:
void find()
{
cout << "you cann't find girlfriend" << endl;
}
};
class find_boyfriend : public find_friend
{
public:
void find()
{
cout << "you cann't find boyfriend" << endl;
}
};
int main()
{
find_friend *f = new find_friend;//f的静态类型是find_friend,动态类型是find_friend
find_girlfriend *f2 = new find_girlfriend;
find_boyfriend f3;
f = f2;//f的动态类型是find_girlfriend
f->find();
f = &f3;//f的动态类型是find_boyfriend
f->find();
cout << "==========" << endl;
find_friend &f4 = *f2;//f4的静态类型是find_friend,动态类型是find_girlfriend
find_friend &f5 = f3;//f5的静态类型是find_friend,动态类型是find_boyfriend
f4.find();
f5.find();
}
/*
you cann't find girlfriend
you cann't find boyfriend
==========
you cann't find girlfriend
you cann't find boyfriend
*/
纯虚函数
没有函数体的虚函数
virtual <类型><函数名>(<参数表>) = 0;参数表>函数名>类型>
= 0告诉编译器该函数没有函数体,是纯虚函数
class people
{
public:
int age;
virtual void print() = 0;//纯虚函数
};
抽象类
包含纯虚函数的类
只能作为基类派生新类,不能创建抽象类的对象
可以创建抽象类的指针和引用
抽象类中的成员函数(构造函数和析构函数除外)可以调用纯虚函数
class people
{
public:
int age;
virtual void print() = 0;//纯虚函数
};
class girlfriend : public people
{
public:
void print()
{
cout << "hello world" << endl;
}
void print2()
{
print();
}
};
int main()
{
//people p1;报错
people *p2;
girlfriend g;
people &p3 = g;
p2 = &g;
p3.print();
p3.print2();
p2->print();
p2->print2();
}
/*
hello world
hello world
hello world
hello world
*/
如果一个类从抽象类派生而来,必须实现基类中所有纯虚函数才能成为非抽象类
class people//抽象类
{
public:
virtual void print() = 0;
virtual void print2() = 0;
};
class girlfriend : public people//非抽象类
{
public:
void print()
{
cout << "hello world" << endl;
}
void print2()
{
cout << "hello world" << endl;
}
};
class boyfriend : public people//没有实现print2,抽象类
{
public:
void print()
{
cout << "hello world" << endl;
}
};
作用:使用基类的指针删除派生类对象。当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。
只有当一个类被用来作为基类的时候,才把析构函数声明为虚函数。
#include<iostream>
using namespace std;
class people
{
public:
virtual ~people()
{
cout << "People is being destroyed" << endl;
}
};
class girlfriend : public people
{
public:
~girlfriend()
{
cout << "Girl_friend is being destroyed" << endl;
}
};
int main()
{
people *p = new girlfriend;
p->~people();
}
/*
Girl_friend is being destroyed
People is being destroyed
*/
虚析构函数工作的方式是:最底层的派生类的析构函数最先被调用,然后各个基类的析构函数被调用。这就是说,即使是抽象类,编译器也要产生对析构函数的调用,所以要保证为它提供函数体。
#include<iostream>
using namespace std;
class people
{
public:
virtual ~people() = 0;
};
class girlfriend : public people
{
public:
~girlfriend()
{
cout << "Girl_friend is being destroyed" << endl;
}
};
int main()
{
people *p = new girlfriend;
p->~people();
}
//报错,需要加上people::~people(){}
若基类析构函数不声明为虚函数,使用delete
当派生类指针指向派生类时,析构函数会先调用派生类析构,释放所有内存。
当基类指针指向派生类时,只会调用基类析构函数,派生类析构函数不被调用,会造成内存泄漏。
#include<iostream>
using namespace std;
class people
{
public:
~people()
{
cout << "People is being destroyed" << endl;
}
};
class girlfriend : public people
{
public:
~girlfriend()
{
cout << "Girl_friend is being destroyed" << endl;
}
};
int main()
{
girlfriend *g = new girlfriend;
people *p = new girlfriend;
delete g;
cout << "==========" << endl;
delete p;
}
/*
Girl_friend is being destroyed
People is being destroyed
==========
People is being destroyed
*/
将基类定义为虚析构函数后,当基类指针指向派生类时,使用delete可以调用派生类析构函数,释放所有的内存,防止内存泄漏
#include<iostream>
using namespace std;
class people
{
public:
virtual ~people()
{
cout << "People is being destroyed" << endl;
}
};
class girlfriend : public people
{
public:
~girlfriend()
{
cout << "Girl_friend is being destroyed" << endl;
}
};
int main()
{
girlfriend *g = new girlfriend;
people *p = new girlfriend;
delete g;
cout << "==========" << endl;
delete p;
}
/*
Girl_friend is being destroyed
People is being destroyed
==========
Girl_friend is being destroyed
People is being destroyed
*/
类的虚函数表是一块连续的内存,每个内存单元中记录一个JMP指令的地址。
虚函数的地址存放于虚函数表之中。
虚表属于类,而不是属于某个具体的对象。同一个类的所有对象都使用同一个虚表。
类的对象内部会有指向类内部的虚表地址的指针(*__vptr ),类的对象在创建时便拥有了这个指针,且这个指针的值会自动被设置为指向类的虚表。 通过这个指针访问虚函数表,然后遍历其中函数指针,并调用相应的函数。
单继承派生类中仅有一个虚函数表,无论派生类有没有重写基类的虚函数,这个虚函数表和基类的虚函数表都不是一个表,但如果派生类没有重写基类的虚函数,基类和派生类的虚函数表指向的函数地址相同。
#include<iostream>
using namespace std;
class people
{
public:
int age;
virtual void print(){}
virtual void print2(){}
virtual void print3(){}
};
class girlfriend : public people
{
public:
void print()
{
cout << "hello world" << endl;
}
virtual void print4(){}
};
class boyfriend : public people
{
public:
};
int main()
{
people p;
girlfriend gf;
boyfriend bf;
}
多继承情况下,派生类中有多个虚函数表,虚函数的排列方式和继承的顺序一致。派生类重写函数将会覆盖所有虚函数表的同名内容,派生类自定义新的虚函数将会在第一个类的虚函数表的后面进行扩充。
#include<iostream>
using namespace std;
class people
{
public:
virtual void print(){}
virtual void print2(){}
};
class find_girlfriend
{
public:
virtual void print3(){}
virtual void print4(){}
};
class damn_single : public people, public find_girlfriend
{
public:
void print3(){}
virtual void print5(){}
virtual void print6(){}
};
int main()
{
people p;
find_girlfriend f;
damn_single d;
}
以适当的偏移值来调整this指针以跳到对应的虚函数中去,并调用这个函数
虚函数表中:
本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议(CC BY-NC-ND 4.0)进行许可。
This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License (CC BY-NC-ND 4.0).