类与对象学习总结2

目录

一、封装(接上篇)

1.初始化列表(用于初始化对象)

2.类对象作为类成员

3.静态成员

4.成员变量和成员函数分开储存

5.this指针

6.空指针访问成员函数

7.友元

8.运算符重载

二、继承

1.基本实现方式

2.继承方式

3.继承中的对象模型

4.继承中的构造和析构顺序

5.多继承语法

6.菱形继承


一、封装(接上篇

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;
}

其对象模型

  • 15
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值