目录
一、封装(接上篇)
1.初始化列表(用于初始化对象)
语法:构造函数:成员1(参数1),成员2(参数2),成员3(参数3){}
#include<iostream>
using namespace std;
class Person{
public:
int A;
int B;
int C;
Person(int a,int b,int c):A(a),B(b),C(c){
cout<<"Person的有参构造函数调用."<<endl;
}
};
void func(){
Person p(10,20,30);
cout<<"A="<<p.A<<endl;
cout<<"B="<<p.B<<endl;
cout<<"C="<<p.C<<endl;
}
int main(){
func();
return 0;
}
2.类对象作为类成员
1.当其它类对象作为本类成员时,先构造类对象,再构造自身
2.析构函数与之相反,符合先进后出的原则
#include<iostream>
using namespace std;
class A{
public:
A(){
cout<<"A的默认构造函数调用"<<endl;
}
~A(){
cout<<"A的析构函数调用"<<endl;
}
};
class B{
public:
A a;//先构造对象,再构造自身
B(){
cout<<"B的默认构造函数调用"<<endl;
}
~B(){
cout<<"B的析构函数调用"<<endl; //谁先构造谁慢析构
}
};
void func(){
B b;
}
int main(){
func();
return 0;
3.静态成员
1.静态成员变量
所有变量共享一份数据
编译阶段分配内存(在全局区)
类内声明,类外初始化
2.静态成员函数
所有对象共享一个函数
静态成员函数只能访问静态成员变量
3.两种访问方式:通过对象访问;通过类名访问
注:静态成员变量一定要在类外初始化
#include<iostream>
using namespace std;
class Person{
public:
static int a;//静态成员变量
int b;
//静态成员函数
static void func(){
a=10;
//b=20;b为非静态成员变量,其不可访问
cout<<"静态成员函数调用"<<endl;
}
private:
static void func2(){
cout<<"私有函数调用"<<endl;
}
};
int Person::a=100;//Person作用域里初始化a (一定要有类外初始化)
void func(){
//静态成员的两种访问方式
//通过对象访问
Person p;
p.func();
cout<<p.a<<endl;
//通过类名访问
Person::func();
cout<<Person::a<<endl;
/*私有静态成员同样不可访问
Person::func2();是错误的*/
}
int main(){
func();
return 0;
}
4.成员变量和成员函数分开储存
只有非静态成员变量才属于类的对象,也就是说所有的静态成员和非静态成员函数都不属于类的对象,我们可以通过打印类的大小来进行验证
空对象的大小为1
#include<iostream>
using namespace std;
class empty{
};//空对象
class Person{
public:
int A;//非静态成员变量,在类的对象上
static int a;//静态成员变量,不属于类的对象
void func(){}//非静态成员函数 ,不属于类的对象
static void Func(){}//静态成员函数,不属于类的对象
};
void func(){
empty e;
cout<<"sizeof e="<<sizeof(e)<<endl;
Person p;
cout<<"sizeof p="<<sizeof(p)<<endl;
}
int main(){
func();
return 0;
}
5.this指针
指向被调用的成员函数所属的对象
this指针是隐含在每一个非静态成员函数的一种指针,不需要去定义,可以直接使用
用途:1.当形参名与成员变量同名时,可用this指针区分
2.在类的非静态成员函数中返回对象本身,可以用return *this
代码示范:
#include<iostream>
using namespace std;
class Person{
public:
Person(int age){
this->age=age;//this指针指向调用该构造函数的对象
}
Person& addAge(Person &p){//引用的方式返回,否则将调用拷贝构造函数返回值
age+=p.age;
return *this;
}
int age;
};
void func(){
Person p1(18);
Person p2(18);
//链式编程思想
p2.addAge(p1).addAge(p1).addAge(p1);//每次返回值都是对象本身,可以再调用成员函数
cout<<"p1的年龄"<<p1.age<<endl;
cout<<"p2的年龄"<<p2.age<<endl;
}
int main(){
func();
return 0;
}
6.空指针访问成员函数
我们可以定义空的指针对象用于访问类的成员函数
不过,需要注意的是,函数是否有使用this指针,this指针指向调用函数的对象,而此时对象为空指针,因此this指针指向空,使用时要进行一些判断
代码示范:
#include<iostream>
using namespace std;
class Person{
public:
Person(){
age=18;
}
void showClassName(){
cout<<"this is Person class"<<endl;
}
void showAge(){
if(this==NULL)
return;
cout<<"age="<<age<<endl;//这里相当于this->age,而this指向调用该函数的对象
}
int age;
};
void func(){
Person *p=NULL;//此时对象为空指针,使得this是空指针
p->showClassName();
p->showAge();
}
int main(){
func();
return 0;
}
7.友元
让类外的好朋友可访问类的私有成员(即让一个函数或另一个类访问类的私有成员)
分为三类:
类做友元;全局函数做友元;成员函数做友元;
关键字:friend 语法:在类开始的第一行加上friend +要作为友元的(类,函数);
示范代码:
#include<iostream>
#include<string>
using namespace std;
class house;
//类做友元
class goodGay2{
public:
house *h;
goodGay2();
void visit();
};
//成员函数做友元
class Friend{
public:
house *h;
Friend();
void True();//让这个函数可以访问house的私有成员,即做友元
void False();//这个不给访问house的私有成员
//另外这里展示成员函数的类外定义
};
class house{
friend void goodGay1(house &h);//声明全局函数做友元
friend class goodGay2;//声明友元类
friend void Friend::True();
public:
string livingRoom;
house(){
livingRoom="客厅";
bedRoom="卧室";
}
private:
string bedRoom;
};
//全局函数做友元
void goodGay1(house &h)
{
cout<<"你的好基友1正在访问->你的"<<h.livingRoom<<endl;
cout<<"你的好基友1正在访问->你的"<<h.bedRoom<<endl;
}
//类内函数的类外定义
goodGay2::goodGay2(){
h=new house;
}
void goodGay2::visit(){
cout<<"你的好基友2正在参观->你的"<<h->livingRoom<<endl;
cout<<"你的好基友2正在参观->你的"<<h->bedRoom<<endl;
}
Friend::Friend(){
h=new house;
}
void Friend::True(){
cout<<"你的普通朋友正通过正当手段访问->你的"<<h->livingRoom<<endl;
cout<<"你的普通朋友正通过正当手段访问->你的"<<h->bedRoom<<endl;
}
void Friend::False(){
cout<<"你的普通朋友正通过正当手段访问->你的"<<h->livingRoom<<endl;
// cout<<"你的普通朋友正通过正当手段访问->你的"<<h->bedRoom<<endl;访问不了
}
int main(){
house h;
goodGay2 g;
Friend f;
goodGay1(h);
g.visit();
f.True();
f.False();
return 0;
}
补充:成员函数的类外定义,在类内声明函数,类外通过作用域::进行定义,即
class 类名{
public:
void print();
}
void 类名::print(){
......
}
8.运算符重载
c++中我们可以通过对运算符进行重载,以实现原本没有的功能,包括对自定义类的一些操作。下面对几个内置的运算符进行重载。关键字:operator
1)对加号+运算符进行重载 operator+
重载实现有两种方式,一种是通过全局函数进行重载,一种是通过类成员函数进行重载,下面演示对加号进行重载:
#include<iostream>
using namespace std;
//运算符重载 关键字operator+
class Person{
public:
int a;
int b;
Person(int a=0,int b=0){
this->a=a;
this->b=b;
}
//用成员函数进行运算符重载
Person operator+(Person &p){
Person temp;
temp.a=this->a+p.a;
temp.b=this->b+p.b;
return temp;
}
};
class dog{
public:
int a;
int b;
dog(int a=0,int b=0){
this->a=a;
this->b=b;
}
};
//用全局函数进行运算符重载
dog operator+(dog &d1,dog &d2){
dog temp;
temp.a=d1.a+d2.a;
temp.b=d1.b+d2.b;
return temp;
}
//运算符重载也可实现函数重载
dog operator+(dog &d1,int x){
dog temp;
temp.a=d1.a+x;
temp.b=d1.b+x;
return temp;
}
int main(){
Person p1(5,6);
Person p2(9,10);
Person p3=p1+p2;//本质Person p3=p1.operator+(p2)
cout<<"p3的a="<<p3.a<<endl<<"p3的b="<<p3.b<<endl;
dog d1(4,6);
dog d2(11,5);
dog d3=d1+d2;//本质Person p3=operator+(d1,d2)
//运算符重载也可函数重载,下面进行类对象与整型相加
dog d4=d3+10;
cout<<"d4的a="<<d4.a<<endl<<"d4的b="<<d4.b<<endl;
cout<<"d3的a="<<d3.a<<endl<<"d3的b="<<d3.b<<endl;
return 0;
}
补:运算符重载也可以发生函数重载;但运算符重载不能改变原先的内置运算规则,只能实现对自定义类的重载运算
2)左移运算符<<的重载
理论上可以用全局函数或者成员函数进行重载,但这里用成员函数无法实现想要的效果
//成员函数实现(但无法实现想要的效果)
ostream& operator<<(ostream &cout){//最终效果 p<<cout;并非想要的效果
cout<<"p.a="<<this->a<<endl<<"p.b="<<this->b;
return cout;
}
所以通常都采用全局函数进行
#include<iostream>
using namespace std;
class Person{
public:
int a;
int b;
Person(int a=0,int b=0){
this->a=a;
this->b=b;
}
//成员函数实现(但无法实现想要的效果)
ostream& operator<<(ostream &cout){
cout<<"p.a="<<this->a<<endl<<"p.b="<<this->b;
return cout;
}
};
//全局函数重载<<左移运算符号
ostream& operator<<(ostream &cout,Person &p){
cout<<"p.a="<<p.a<<endl<<"p.b="<<p.b;
return cout;//返回一个cout的引用,这样可以链式编程
}
int main(){
Person p(10,20);
Person P(20,16);
cout<<p<<endl;
P<<cout<<endl;
return 0;
}
3)自增/自减运算符重载
分为前置和后置两种,对于前置自增运算符,先进行自增操作再计算表达式的值,而后置自增则是先计算表达式的值再进行自增操作,两者就不太一样了。
前者的重载函数中,只需要对对应的成员进行操作,然后返回对象本身就好了(返回引用)
而后者,需要对原本的对象进行暂时储存,接着进行操作,最后返回的仍是原本的对象,此时要通过值的形式返回,因为创造的中间变量为局部变量,在栈上,函数执行完即被销毁。
代码示例:
#include<iostream>
using namespace std;
class Myint{
friend ostream& operator<<(ostream &cout,Myint &p);
int a;
public:
Myint(){
a=0;
}
Myint& operator--(){
a--;
return *this;
}
Myint operator--(int){
Myint temp=*this;
a--;
return temp;//temp为局部变量
}
};
ostream& operator<<(ostream &cout,Myint &p){
cout<<p.a;
return cout;//这样返回可以实现链式编程
}
int main(){
Myint m_i;
cout<<--m_i<<endl;//m_i.opertor--()
cout<<m_i--<<endl<<m_i<<endl;
return 0;
}
4)关系运算符重载
这里主要是对==和!=的重载,其他的关系符号类似;
下面示例重载==和!=运算符,进行两个类对象之间的判断
#include<iostream>
#include<string>
using namespace std;
//关系运算符重载
class Person{
public:
int age;
string name;
Person(string name,int age){
this->name=name;
this->age=age;
}
//可以用0/1做返回值,也可用布尔类型
int operator==(Person &p){
if(name==p.name&&age==p.age)
return 1;
return 0;
}
bool operator!=(Person &p){
if(name!=p.name&&age!=p.age)
return true;
return false;
}
};
int main(){
Person p1("张三",18);
Person p2("张三",18);
Person p3("李四",45);
if(p1==p2)
cout<<"p1与p2相等!"<<endl;
if(p1!=p3)
cout<<"p1与p2不相等!";
return 0;
}
5)赋值运算符重载
c++编译器会提供4个默认函数
1.默认构造函数 ;2.默认析构函数 ;3.默认拷贝构造函数 ;4.operator=函数用于拷贝赋值操作
在前面讲到过,c++默认的拷贝构造函数是浅拷贝,即通过赋值号将一个对象中的值简单的拷贝到另一个对象中。而当对象中存在堆区数据时,就会在释放内存时导致重复释放,这时就需要进行深拷贝。这里我们重载=,让其在拷贝时,为对象重新在堆区上开辟内存,以防止内存的重复释放
#include<iostream>
using namespace std;
class Person{
public:
int *age;
Person(int age){
this->age=new int(age);
}
//析构函数浅拷贝下可能会重复释放内存,对某些编译器会出问题
~Person(){
if(age!=NULL){
delete age;
age=NULL;
}
}
//因此,重载赋值运算符
Person& operator=(Person &p){
//age=p.age;浅拷贝
//先判断是否有堆区内存,并释放
if(age!=NULL){
delete age;
age=NULL;
}
//重新在堆区上开辟空间
age=new int(*p.age);
return *this;//确保可以实现链式编程
}
};
int main(){
Person p1(18);
Person p2(20);
Person p3(30);
cout<<"p1的年龄:"<<*p1.age<<endl<<"p2的年龄:"<<*p2.age<<endl<<"p3的年龄:"<<*p3.age<<endl;
p3=p2=p1;
cout<<"p1的年龄:"<<*p1.age<<endl<<"p2的年龄:"<<*p2.age<<endl<<"p3的年龄:"<<*p3.age;
return 0;
}
6)函数调用符号的重载
重载后使用非常像函数调用,因此称为仿函数;
仿函数使用十分灵活,没有固定写法。
#include<iostream>
#include<string>
using namespace std;
class Print{
public:
//没有固定写法,返回值和参数也不固定
void operator()(string name){
cout<<name<<endl;
}
int operator()(int a,int b){
return a+b;
}
};
class counter{
public:
int operator()(int a,char c,int b){
switch(c){
case '+':return a+b;break;
case '-':return a-b;break;
case '*':return a*b;break;
case '/':return a/b;break;
default:return 0;break;
}
}
};
int main(){
Print print;
print("张三");
Print add;
int sum=add(10,10);
cout<<"sum="<<sum<<endl;
counter count;
int arr=count(1,'*',2);
cout<<"arr="<<arr<<endl;
cout<<"匿名1+3="<<counter()(1,'+',3);//匿名函数对象
return 0;
}
补:另外,可以通过匿名函数对象进行操作,用完即丢弃;
二、继承
继承是c++面向对象的第二大特性
1.基本实现方式
用于类之间继承公共的成员,继承后子类可以使用父类的成员;
语法:class 类名:继承方式 子类;
其中 子类也叫派生类;
父类也叫基类
下面通过一个实例来进行展示,对洛谷网站页面数据进行整理输出
#include<iostream>
using namespace std;
class web{
public:
//公共头部
void head(){
cout<<"网页公共头部(一幅风景画,题目列表)"<<endl;
}
//公共底部
void foot(){
cout<<"网页公共尾部(风景画,相关人员工作室信息)"<<endl;
}
//公共标签目录
void content(){
cout<<"标签目录(洛谷、主题库、入门与面试)"<<endl;
}
};
//洛谷题库页面
class luogu:public web{
public:
//自己的成员
void self(){
cout<<"洛谷题库的题目...."<<endl;
}
};
///主题库页面
class Main:public web{
public:
void self(){
cout<<"主题库的题目...."<<endl;
}
};
//入门与面试页面
class interview:public web{
public:
void self(){
cout<<"入门与面试题库的题目...."<<endl;
}
};
void func(){
luogu l;
cout<<"-----------------------------------------------------"<<endl;
cout<<"洛谷的网页格局"<<endl;
l.head();
l.content();
l.self();
l.foot();
cout<<"-----------------------------------------------------"<<endl;
cout<<"主题库的网页格局"<<endl;
Main M;
M.head();
M.content();
M.self();
M.foot();
cout<<"-----------------------------------------------------"<<endl;
cout<<"入门与面试的网页风格"<<endl;
interview i;
i.head();
i.content();
i.self();
i.foot();
}
int main(){
func();
return 0;
}
2.继承方式
1.公共继承public:继承后的成员访问权限与父类相同,但父类中的私有权限不可访问;
2.保护继承protected:继承后的成员访问权限变为protected(除私有权限外),父类的私有权限仍访问不到;
3.私有继承private:继承后的成员访问权限为private,父类的私有权限访问不到
总结:继承方式为子类中继承得到的成员的最高访问权限,若高于则降为该权限,且父类的私有权限子类无论如何也访问不到
下面进行演示:
#include<iostream>
using namespace std;
class father{
public:
int a;
protected:
int b;
private:
int c;
};
//公共继承
class son1:public father{
public:
son1(int a,int b,int c){
this->a=a;
this->b=b;
// this->c=c;无法访问
}
void print(){
cout<<"b="<<b<<endl;
}
};
//保护继承
class son2:protected father{
public:
son2(int a,int b,int c){
this->a=a;
this->b=b;
// this->c=c;无法访问
}
void print(){
cout<<"a="<<a<<" "<<"b="<<b<<endl;
}
};
//私有继承
class son3:private father{
public:
son3(int a,int b,int c){
this->a=a;
this->b=b;
// this->c=c;无法访问
}
};
class grandson:public son3{
public:
void set(){
// a=100;
// b=200;父类中私有权限,这里访问不到
}
};
int main(){
son1 s1(1,2,3);
cout<<"s1 a="<<s1.a<<" ";
// cout<<"s1 b="<<s1.b;保护权限类外无法访问
s1.print();//保护权限类内可以访问
son2 s2(1,2,3);
// cout<<s2.a<<endl; 保护权限类外无法访问
cout<<"s2 ";
s2.print();
son3 s3(1,2,3);
// cout<<s3.a<<endl;私有权限类外无法访问
return 0;
}
3.继承中的对象模型
探究从父类继承过来的成员是否属于子类:
下面有两个方法:一是通过打印子类对象的大小来知道继承后的对象结构
#include<iostream>
using namespace std;
class father{
public:
int a;
protected:
int b;
private:
int c;
};
class son:public father{
public:
int d;
};
int main(){
cout<<"size of son "<<sizeof(son);
//结论:无论在父类中的访问权限如何,继承后均属于子类,哪怕是私有权限也发生了继承,只是子类无法访问
return 0;
}
另一种是通过visual studio的命令行工具
通过命令行工具
首先进入文件所在的盘符,接着用cd指令进入文件目录,然后通过下面指令查看对象模型
从中我们可以更深入的了解到继承的一个底层本质
4.继承中的构造和析构顺序
继承中,子类对象创建时会先创建父类对象,在创建子类对象(先有父亲,再有儿子)
而对象销毁时则是相反的,总的来说,符合先进后出的原则(或者通过递归思想)
#include<iostream>
using namespace std;
//继承中构造和析构的顺序
class father{
public:
father(){
cout<<"father的构造函数"<<endl;
}
~father(){
cout<<"father的析构函数"<<endl;
}
};
class son:private father{
public:
son(){
cout<<"son的构造函数"<<endl;
}
~son(){
cout<<"son的析构函数"<<endl;
}
};
int main(){
son s;
return 0;
}
5.多继承语法
多继承,子类可以继承多个父类,就是将不同的父类按继承对象通过逗号排开
语法:class 子类名:继承方式 父类名1,继承方式 父类名2
对于不同父类中的同名成员通过作用域::来区分
#include<iostream>
using namespace std;
class father{
public:
int a;
father(){
a=100;
}
};
class mother{
public:
int a;
mother(){
a=200;
}
};
class son:public father,public mother{
public:
int c;
son(){
c=300;
}
};
int main(){
son s;
cout<<"son-c="<<s.c<<endl;
//通过作用域来区分
cout<<"father-a="<<s.father::a<<endl;
cout<<"mother-a="<<s.mother::a<<endl;
return 0;
}
注:c++开发中不推荐用多继承
6.菱形继承
下面是菱形继承一个形象的示意图
// *******
// *******
// * *
// * *
// * *
// ****** ******
// ****** ******
// * *
// * *
// * *
// ********
// ********
最后一个子类继承了上两个父类的成员,而上两个父类也有共同的成员来自它们的父类。这就导致了最后一个子类中有些成员继承了两份,但实际只需要一份
这时我们采用虚继承来解决这个问题,关键字:virtual,加在继承方式前面,此时父类称为虚基类
虚继承后之前重复继承的成员将变为一个
#include<iostream>
using namespace std;
class animal{
public:
int age;
};
//继承前加virtual关键字变为虚继承
//此时公共父类叫虚基类
class sheep:virtual public animal{};
class tuo:virtual public animal{};
class sheeptuo:public sheep,public tuo{};
int main(){
sheeptuo st;
//通过作用域区分
st.sheep::age=8;
st.tuo::age=28;
st.age=18;//虚继承之前会报错
cout<<"st.sheep::age="<<st.sheep::age<<endl;
cout<<"st.tuo::age="<<st.tuo::age<<endl;
cout<<"st.age="<<st.age<<endl;
//实际只需要一个年龄
return 0;
}
其对象模型