类和对象---

目录

封装:

封装的意义:

关于访问权限:

class和struct的区别:

成员属性设置为私有:

案例一:

案例二:

对象特征:

构造函数和析构函数:

构造函数的分类及调用:

拷贝函数的调用时机:

拷贝函数的调用规则:

深拷贝与浅拷贝:

初始化列表

类对象作为类成员

静态成员

C++对象模型和this指针

 this指针概念(重点)

空指针访问成员函数

const 修饰成员函数

友元

全局函数做友元

友元类

成员函数作友元

运算符重载

加号运算符重载

全局变量重载加号运算符

重载左移运算符

递增运算符的重载

赋值运算符的重载

重载关系运算符

继承

继承的基础语法

继承方式

继承中的对象模型 

继承中父类和子类构造和析构的顺序

继承同名成员处理方式

继承-同名静态成员处理

继承语法

菱形继承问题

虚继承

多态

多态的基本语法

多态的原理剖析

多态的案例

纯虚函数和抽象类

案例二

虚析构和纯虚析构

案例三

文件操作

文本文件

二进制文件

基于多态的职工管理系统 案例

函数模板

函数模板的基本语法

函数模板注意事项

函数模板案例-排序

普通函数和函数模板的区别

普通函数和函数模板调用规则

模板的局限性

类模板

类模板和函数模板的区别

类模板中成员函数的创建时机

类模板对象作函数参数

类模板与继承

类模板成员函数的类外实现

类模板分文件编写

类模板与友元

类模板案例-数组类封装

STL

STL vector

vector存放内置数据类型

vector存放自定义数据类型

vector容器嵌套vector容器

STL string容器

string-构造函数

string的赋值操作

string字符串拼接

string字符串查找和替换

string字符串比较

string的访问和修改

string的插入和删除

string的子串获取

Vector容器-

vector构造函数

vector的赋值操作

vector的容量和大小

vector的插入和删除

vector的数据存取

vector的互换容器

vector-预留空间

STL-deque容器

deque-构造函数

deque的赋值

deque大小操作

deque的插入和删除

deque数据存取

deque排序操作

案例

List链表

List容器的构造函数

List赋值和交换

list容器的大小操作

list链表的插入和删除

list的数据存取

list中的反转和排序

list案例

Set容器

set-构造和赋值

set的大小和交换

set的插入和删除

set的查找和统计

Pair对组的创建和使用

set的内置类型指定排序规则

map容器

map-构造和赋值

map的大小和交换

map的插入和删除

map的查找和统计

map的排序

案例

函数对象-函数对象基本使用(仿函数)

一元谓词、二元谓词

内建函数对象

算术仿函数

关系仿函数

逻辑仿函数

常用遍历算法

for_each//遍历容器

transform//拷贝容器到新容器

常用查找算法

find//查找算法

find_if//按照条件查找元素

3.adjacent_if()//查找相邻重复元素

4.binary_find()//二分算法 查找某元素是否存在

5.count//统计元素出现个数

6.count_if()//按条件统计元素出现个数

常用排序算法

random_shuffle()//打乱容器中元素的顺序仅此而已

merge//合并两个容器到一个新容器里面

reverse//实现容器元素反转

常用拷贝和替换算法

copy//相当于拷贝构造函数但是记得给新容器提前分配空间

replace//将指定旧元素替换为新元素

replace_if//按照条件替换

swap//老朋友了

常用算术生成算法

accumulate//将容器内的元素加和

fill//填充

常用集合算法

set_intersection()//交集

set_union()//求并集

set_difference()//求差集

c++面向对象的三个特性:封装、继承、多态。

对象具有属性和行为,具有相同性质的对象称为类。

(人属于人类,车属于车类。)

封装:

封装的意义:

在设计类的时候,属性和行为写在一起,表现事物。

const double PI=3.14;
class Circle{
	public:
		int r;	//属性 
	double S(){	//行为  
		return  PI*r*r;
	}
}; 
int main(){
	//实例化:通过一个类创建一个对象 例如 通过圆类创建出c1这个圆  
	Circle c1;
	cin>>c1.r;
	cout<<c1.S()<<endl;
	return 0; 
}

类中包含属性和行为。

例如:圆类中,周长是它的属性,面积是它的行为。属性一般是个变量,行为一般是函数。

实例化:通过一个类创建一个对象。

例如:根据圆类创建了c1这个对象。

class Student{
	public:
		int id;
		string name;
	void ShowStudent(){
		cout<<name<<" "<<id;
	}
}; 
int main(){
	//实例化:通过一个类创建一个对象 例如 通过圆类创建出c1这个圆  
	Student s1;
	cin>>s1.name>>s1.id;
	s1.ShowStudent();
	return 0; 
}

一个随便的学生系统,包含姓名和学号。

public里面name类型换成char以后输出会变得奇怪。

因为char是只可以输入一个字符,string可以输入一个串。

关于访问权限:

1.public(类内可以访问,类外也可以访问)

2.protected(类内可以访问,类外不可以访问)

3.private(类内可以访问,类外不可以访问)

#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
class person{
	public:
		string m_name;
	protected:
		string m_car;
	private:
		int m_id;
	public:
		void func(){
			m_name="老刘";
			m_car="卡车";
			m_id=666666 ;
		}
};
int main(){
	person a;
	cout<<a.m_name;
	return 0;
}
	

如果想要输出 a.m_car 和 a.m_id 的话是不能实现的

因为分别是protected 和 public 只能在类内访问 

也就是说只能在person中 进行访问保护权限和私有权限

class和struct的区别:

class的默认权限是私有

struct的默认权限是公共

原因是

class 和 struct 最本质的区别 :

class 是引用类型,它在堆中分配空间,栈中保存的只是引用;

而 struct 是值类型,它在栈中分配空间。

(29条消息) 【C/C++面试必备】struct和class的区别_Linux猿的博客-优快云博客

转载一下大佬的一个文章,关于class和struct的区别。

成员属性设置为私有:

优点1:将成员属性设置为私有,可以控制读写权限。

优点2:对于写权限,我们可以检测数据的有效性。

class Person{
	public:
		//可读可写 
		void set_name(string Name){
			name=Name;
		} 
		string get_name(){
			return name;
		} 
		//只可写 
		void set_age(int Age){
			Age=age;
		}
		//只可读 
		string get_lover(){
			lover="老六";
			return lover;
		}
		
	private:
		//可读可写 
		string name;
		//只可写 
		int age;
		//只可读 
		string lover;
}; 
int main(){
	Person a; 
	//可读可写 
	string Name="老三";
	a.set_name(Name);
	cout<<"name:"<<a.get_name()<<"\n";
	//只可写
	a.set_age(10);
	//只可读 
	cout<<"lover: "<<a.get_lover(); 
	return 0;
}

上面便是可读可写、可读、可写的操作 ,如此控制了读写的权限

关于写的权限可以通过条件语句控制其有效性。

class Person{
	public:
		void set_age(int Age){
			age=Age;
		}
		int get_age(){
			if(age<0 || age>150){
				cout<<"年龄不符"<<"\n";
				return 0;
			}
			else{
				cout<<"年龄符合"<<"\n";
				return age;
			}
		}
	private:
		int age;
}; 
int main(){
	Person a; 
	int Age;cin>>Age;
	a.set_age(Age);
	a.get_age();
	return 0;
}

通过条件语句实现 对于写权限检测数据的有效性

案例一:

通过全局函数成员函数来判断两个立方体是否完全相等

#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
class Cube{
	public:
		//设置长 宽 高 
		void set_L(int L){
			m_L=L;
		}
		void set_W(int W){
			m_W=W;
		}
		void set_H(int H){
			m_H=H;
		}
		//获取长 宽 高 
		int get_L(){
			return m_L;
		}	
		int get_W(){
			return m_W; 
		}
		int get_H(){
			return m_H;
		}
		//求表面积 
		int calculateS(){
			return 2*(m_L+m_W+m_H);
		} 
		int calculateV(){
			return m_L*m_W*m_H;
		}
		bool IsSame2(Cube &c){
			if(c.m_L==m_L&&c.m_H==m_H&&c.m_W==m_W)	 return 1;
			else					 				 return 0;
		}
	private:
		//设置长 宽 高 
		int m_L;
		int m_W;
		int m_H;
				
}; 
bool IsSame1(Cube &c1,Cube &c2){
	if(c1.calculateS()==c2.calculateS() && c1.calculateV()==c2.calculateV())
		return 1;
	else
		return 0;
}
int main(){
	int L,W,H;
	cin>>L>>W>>H;
	Cube c1,c2;
	c1.set_L(L);c1.set_W(W);c1.set_H(H);
	c2.set_L(L);c2.set_W(W);c2.set_H(H);
	if(IsSame1(c1,c2))	 cout<<"相等"<<endl;
	else			 	 cout<<"不相等"<<endl;
	
	if(c1.IsSame2(c2)) cout<<"相等"<<"\n";
	else				 cout<<"不相等"<<"\n";
	
	return 0;
}

重点在于:

全局函数需要引用两个立方体:

bool IsSame1(Cube &c1,Cube &c2){
    if(c1.calculateS()==c2.calculateS() && c1.calculateV()==c2.calculateV())
        return 1;
    else
        return 0;
}

这里分别引用了了c1和c2,通过比较长宽高来确定是否完全相等。

成员函数只需要引用一个立方体:

bool IsSame2(Cube &c){
            if(c.m_L==m_L&&c.m_H==m_H&&c.m_W==m_W)     return 1;
            else                                      return 0;
        }

........

if(c1.IsSame2(c2)) cout<<"相等"<<"\n";  
    else                 cout<<"不相等"<<"\n";

引用一个立方体,这样在c1的成员函数中引用c2进而判断是否相等。

案例二:

判断点和圆的关系(重点是如何在另一个类里面引用另一个类)


class Circle{
	public:
		void set(){
			cin>>x1>>y1;
		}
		int rr(){
			return x1*x1+y1*y1;
		}
	private:
		int x1,y1;
};
class Point{
	public:
		void get1(){
			c.set();
		}	
		void get2(){
			cin>>x2>>y2;
		}	
		void Judge(){
			if((x2*x2+y2*y2)==c.rr()) 		cout<<"在圆上"<<"\n";
			else if((x2*x2+y2*y2)>c.rr())	cout<<"在圆外"<<"\n";
			else 							cout<<"在圆内"<<"\n";
		}
	private:
		Circle c;
		int x2,y2;
};
int main(){
	Point p;
	p.get1();p.get2();
	p.Judge();
	return 0;
} 

在private 里面定义属性 那么需要在public里面通过行为对属性赋值

在另一个类里面引用类,前面那个是自己引用自己

private:
		Circle c;
		int x2,y2;

在Point类里面引用Circle类

public:
		void get1(){
			c.set();
		}	

通过Circle类里面的行为来进行赋值

对象特征

构造函数和析构函数

构造函数:主要作用于创建对象时为对象成员属性赋值,构造函数由编译器自动调用,无需手动。

析构函数:主要作用于对象销毁前系统自动调用,执行清理工作。

构造函数语法:类名(){}

1.没有返回值,不需要void。

2.函数名称和类名相同。

3.构造函数可以有参数,可以发生重载。

4.程序在调用对象时会自动调用,只会调用一次,无需手动。

析构函数语法:~类名(){}

1.没有返回值,不需要void

2.函数名称和类名相同 不过需要加上~

3.析构函数不可以有参数不可以发生重载。

4.程序在对象销毁前会自动调用,只会调用一次,无需手动。

构造函数的分类及调用:

两种分类方式:

按照参数:有参构造和无参构造

    Person(){
		cout<<"无参构造"<<"\n"; //默认构造函数
	}
	Person(int a){
		age=a;
		cout<<"有参构造"<<"\n";
	}

按照拷贝:普通构造和拷贝构造

Person(){
		cout<<"无参构造"<<"\n"; 
	}
Person(const Person &p){
		age=p.age;
		cout<<"拷贝构造"<<"\n"; 
	}

三种调用方式:

1.括号法

//1.括号法 
	cout<<"括号法"<<"\n"; 
	Person p1;
	Person p2(10);
//	cout<<"p2的年龄:"<<p2.age<<"\n";
	Person p3(p2);
//	cout<<"p3的年龄:"<<p3.age<<"\n";

这里输出了p2 p3的年龄 可以体现拷贝构造函数的作用

2.显式法

//2.显式法
	cout<<"显式法"<<"\n";
	Person pp1;
	Person pp2=Person(10);
	Person pp3=Person(pp2);
	//注意:
	//Person(10)是个匿名对象 当前行执行结束后立马回收  
	//注意:
	//不要用拷贝构造去初始化匿名对象  这样会出现重定义的错误 例如:Person(p3)==Person p3;会被编译器默认为相等 

Person(10)是个匿名对象 当前行执行结束后立马回收 。

不要用拷贝构造去初始化匿名对象  这样会出现重定义的错误

(不要在匿名函数里面创建一个已知的拷贝构造函数)

注意:p3是之前的一个拷贝构造函数

例如:Person(p3) 等价于Person p3; 会被编译器默认为等价。

"Person(10)"里面的Person编译器理解成Person的构造函数,传入参数10调用有参构造函数。

"Person(p3)"里的Person编译器理解成Person类型,整个语句意思就变成了定义一个Person类的变量p3。

Person(p2)或者Person(p1)都不可以。

3.隐式转换法

	//3.隐式转换法 
	cout<<"隐式转换法"<<"\n";
	Person ppp1;
	Person ppp2=10;//相当于Person ppp2=Person(10); 
	Person ppp3=ppp2;//相当Person ppp3=Person(ppp2);

拷贝函数的调用时机:

class Person{
	public:
		int age;
	public:
		Person(){
			cout<<"默认构造函数"<<"\n";
		}	
		Person(int Age){
			age=Age;
			cout<<"有参构造函数"<<"\n";
		}
		Person(const Person &p){
			age=p.age;
			cout<<"拷贝构造函数"<<"\n";
		} 
		~Person(){
			cout<<"析构函数"<<"\n";
		}	
}; 

//先定义一个Person 类

1.使用一个已经创建完毕的对象来初始化另一个新对象

void test1(){
	Person p1(20);
	Person p2(p1);
	cout<<"p2的年龄"<<p2.age<<"\n";
}

用括号法 用20给p1赋值,调用有参构造函数

然后用p1给p2赋值,调用拷贝构造函数

2.值传递的方法给函数参数传值

void func1(Person p4){
	
}
void test2(){
	Person p3;
	func1(p3);
}

Person p3调用默认构造函数,调用func1(),进行值传递。

值传递的过程中进行了拷贝构造函数的使用。

值传递中Person p4=p3 等价于 Person p4(p3);

这便用到了拷贝构造函数

3.值方式返回局部变量 

Person func2(){
	Person p5;
	return p5;
}
void test3(){
	Person p6=func2();
}

因为有编译器的自动优化所以只会显示出默认构造函数

关掉后就会出现拷贝构造函数

拷贝函数的调用规则:

创建一个类,c++编译器每个类添加至少三个函数

//默认构造(空实现) 空实现即不进行其他的操作

//默认析构(空实现)

//拷贝构造(对类中的属性进行值拷贝)

1.如果写了一个有参构造函数,那么编译器就不会提供默认构造函数。

2.如果写了一个拷贝构造函数,那么编译器就不会提供 默认构造和默认析构函数。

深拷贝与浅拷贝:

浅拷贝:简单的赋值拷贝操作

深拷贝:在堆区重新申请内存空间,进行拷贝操作

浅拷贝:

class Person {
	public:
		int m_age;
		int* m_height;
	public:
		Person(int age, int height) {
			m_age = age;
			m_height = new int(height);
			cout << "有参构造函数" << "\n";
		}
		~Person() {
			if (m_height != NULL) {
				delete m_height;
				m_height = NULL;
			}
			cout << "析构函数" << "\n";
		}
};
void func() {
	Person p1(18, 180);
	Person p2(p1);
}

在 C++ 中,使用 new 运算符动态创建的对象默认返回的是指针类型

当执行 Person p2(p1)的时候便执行了 浅拷贝

此时将堆区的某个地址再次赋值给了p2的m_height。

然后进行释放操作(即delete操作)是重复释放了同一地址的内存。

造成出错。

如图所示 

因此浅拷贝带来的错误需要用深拷贝进行解决。

(即自己写一个拷贝构造函数重新创建一个地址并赋给变量)

Person(const Person& p) {
			cout << "拷贝构造函数" << "\n";
			m_age = p.m_age;
			//m_height=p.m_height 编译器默认执行这个操作所以出现浅拷贝 所以出错
			m_height = new int(*p.m_height);
		}

这样 p2的m_height的地址就和p1的不同了 从而不会重复清空内存

最后总结一下:

解决浅拷贝 就是自己在堆区重新申请内存空间,进行拷贝操作.

如果需要在堆区进行开辟空间,那么自己一定要写一个拷贝构造函数,防止浅拷贝出错。

浅拷贝:简单的赋值拷贝操作  

深拷贝:在堆区重新申请内存空间,进行拷贝操作

初始化列表

class Person{
	public:
		int m_a,m_b,m_c;
	public:
		Person(int a,int b,int c):m_a(a),m_b(b),m_c(c){
		}
		
};
void func1(){
	Person p(10,20,30);
	cout<<p.m_a<<" "<<p.m_b<<" "<<p.m_c<<"\n";
}

相当于给属性进行一次快速的赋值操作。

这样的好处是:

如果每一层基类/派生类通过初始化列表的方式,则不管继承包裹了多少层,完成类成员变量的构造,只调用一次成员自己的构造函数。


class A{
	public:
		int m_a;
	public:
		A(int a):m_a(a){
			cout<<m_a<<"\n";
		}	
};
class B:public A{
	public:
		int m_b;
	public:
		B(int b):A(b),m_b(b){
			cout<<m_b<<"\n";
		}	
};
class C:public B{
	public:
		int m_c;
	public:
		C(int c):B(c),m_c(c){
		}	
};
void func1(){
	C D(20);
	cout<<D.m_c<<"\n";
}
int main(){
	func1();	
	system("pause");
}

如上是类的嵌套中 初始化列表的使用。

类对象作为类成员


class Phone{
	public:
		string p_name;
	public:
		Phone(string name3){
			p_name=name3;
			cout<<"Phone的构造"<<"\n";
		}	
		~Phone(){
			cout<<"Phone的析构"<<"\n";
		}
}; 
class Person{
	public:
		string m_name;
		Phone p;
	public:
		Person(string name2,string name3):m_name(name2),p(name3){
		}
		
//		Person(){
//			cout<<"Person的构造"<<"\n";
//		}
		~Person(){
			cout<<"Person的析构"<<"\n";
		} 
};


int	main(){
	Person p1("老大","华为");
	cout<<p1.m_name<<" "<<p1.p.p_name<<"\n";
	system("pause");
}

相当于类的嵌套。

在一个类中调用另一个类的属性。

//		Person(){
//			cout<<"Person的构造"<<"\n";
//		}

如果不对这个Person()进行注释那么可能会出现重定义的错误。(我认为的可能)

静态成员

注意关键词:static

静态成员分为静态成员变量和静态成员函数

静态成员变量的特点:

1.所有对象共享同一份数据。

2.在编译阶段分配内存。(程序还未运行就已经在全局区分配好了内存)

3.类内声明,类外初始化。

栈区和堆区位于动态存储区域,全局变量和局部静态变量位于静态存储区域。

调用静态成员变量的方法有两种:用类名进行调用、用对象进行调用。


class Person{
	public:
		//1.所有对象共享同一份数据 
		//2.编译前就分配好了空间 
		//3.类内声明 类外访问 
		static int a;
};
int Person::a=10; 
int main(){
	Person p;
	cout<<p.a<<"\n";
	cout<<Person::a<<"\n";
}

Person::a 指的是调用Person类中的成员变量a,用类名进行调用。

int Person::a=10;  一定要在类外进行一次声明否则报错 。声明可以不赋值,默认为零。


class Person{
	public:
		//1.所有对象共享同一份数据 
		//2.编译前就分配好了空间 
		//3.类内声明 类外访问 
		static int a;
};
int Person::a=10; 
int main(){
	Person p;
	cout<<p.a<<"\n";
	Person p1;
	cout<<p1.a<<"\n";
	return 0;
}

无论是p.a 还是 p1.a 结果都是10.(说明所有对象共享同一份数据

当对 对象中的静态成员变量 进行修改,修改后静态成员变量发生变化。

静态成员变量在编译前就已经在全局区被分配空间所以会根据修改而变化。

	Person p;
	cout<<p.a<<"\n";
	Person p1;
	p1.a=50;
	cout<<p1.a<<"\n";

例如:前者是10,修改后则是50。

静态成员变量同样也有访问权限。

如果是private中定义的静态成员变量那么将无法在类外访问。

静态成员函数的特点:

1.所有对象共享同一个成员静态函数

2.静态成员函数只能访问静态成员变量

访问的方式还是那两种:1.通过对象进行访问               2.通过类名进行访问

class Person {
	public:
		static int func() {
			a = 100;
			b = 200;
			cout << "静态成员函数" << "\n";
		}
	public:
		int b;
		static int a;
};

静态成员函数只能访问静态成员变量。

静态成员在编译前就已经分配好地址,而非静态成员只有在对象实例化后才存在。

静态成员函数存在在前,非静态成员变量存在在后,故而不可访问。

C++对象模型和this指针

成员变量和成员函数分别存储

一个空的对象其占用的内存空间为0。

class Person{
	
	
};
int main(){
	Person p;
	cout<<sizeof(p)<<"\n";	

编译器给空对象内存空间是为了区分空对象所占内存位置。

每个空对象也应该有一个独一无的内存地址。

非静态成员变量属于类的实例化对象。

例如:如果对象为空那么其内存空间则为1,如果对象里有非静态成员变量且为int类型那么其内存空间为4.

有个知识点:内存对齐。

内存对齐:以32位为例,如果对象中仅有一个char的非静态成员变量那么其内存空间大小位1,

如果是一个int 一个char 那么根据内存对齐其内存空间大为8

如果是一个string一个int 那么根据内存对齐其内存空间大小为16.

即按照最大的数据类型所占内存来,乘以变量个数即为该对象内存空间。

静态成员函数 静态成员变量 非静态成员函数 均不属于类的对象。

 this指针概念(重点)

每一个非静态成员函数只会诞生一份函数实例,也就是说多个类型的对象用同一块代码。

this指针便是区分该代码哪个对象是进行调用的。

this指针 指向 被调用的成员函数所属的对象

this指针的用途:

1.当形参和成员变量同名时,用this指针来区分。

class Person{
	public:
		int a;
	public:
		void func(int a){
			a=a;
		}
};
int main(){
	Person p;
	p.func(10);
	cout<<p.a<<"\n";

此时的p.a输出为0。

因为函数无法区分形参和成员变量时,默认使用最近的参数,自然a=a。

此时,

void func(int a){
			this->a=a;
		}

使用this指针(this指针指向被调用成员函数所属的对象,即this指针属于类内

this->a就等价于 p.a 则值为10.

在类的非静态成员函数中返回对象本身可使用 return *this(相当于解指针操作)

提问:静态函数有没有this指针?

回答:没有 因为 静态成员函数 静态成员变量 非静态成员函数 不属于对象(抬头骄傲)

this指针 指向 被调用的成员函数的 对象

 对象指的是类创建的一个具体化的实例。

class Person {
public:
	int m_Age;
public:  
	Person(int age) {
		m_Age = age;
	}
	Person &Add_age(Person p) {
		m_Age += p.m_Age;
		return *this;
	}
};
int main() {
	Person p1(10);
	Person p2(10);
	p1.Add_age(p2).Add_age(p2).Add_age(p2);
	cout << p1.m_Age << "\n";

链式编程思想

例如:p1.Add_age(p2).Add_age(p2).Add_age(p2);
还有例子 cout<<a<<b<<c<<"\n"; 这俩均是链式编程思想

我这里实现了p1加p2的年龄的链式,非常值得一提的是。

Person &Add_age(Person p)  这个“&”很重要 

用了“&”以后是引用传递返回的还是p1这个对象。如果不用“&”那么就是值传递,值传递的过程中需要调用拷贝构造函数,每次调用一次就会产生一个新的匿名对象,而p1本体的年龄未发生变化。

这里需要回顾前面 拷贝构造函数的调用时机:

1.用一个已创建的对象给另一个对象赋值

2.用值传递的方式给函数参数传值

3.用值传递的方式返回局部变量

最后 提一下 return *this 返回的是此函数的对象本身

如上,如果用&那么返回的就一直是p1。如果不用&那么返回的就一直是新的匿名对象。

空指针访问成员函数

class Person {
public:
	int m_Age;
public:  
	void func1() {
		cout << "func1" << "\n";
	}
	void func2() {
		cout << "func2" << "\n";
		cout << m_Age << "\n";
	}
	
};
int main() {
	Person * p = NULL;
	p->func1();
	p->func2();

当进行p->func1()时,程序没问题。但,进行到p->func2()时,程序报错.

因为func2()中有m_Age这个成员变量,而所创建的空指针里面什么都没有因此访问成员变量会报错。

进行cout<<m_Age<<"\n"的时候,默认为cout<<this->m_Age<<"\n".

this指针指向被调用的成员函数的对象

 创建的空指针对象啥都没有,因此出错。

const 修饰成员函数

常函数:

1.成员函数加上const后被称为 常函数

2.常函数内不可修改成员属性.

3.成员变量加上mutable这个关键字后,常函数可以对其进行修改。

class Person {
	public:
		int m_A;
		mutable int m_B;//特殊变量,常函数可以对其进行修改.
	public:
		void showPerson()const {
			//m_A = 500; 例如这个没有加关键字mutable 那么常函数就不能对其进行修改
			m_B = 500;
		}

};
int main() {
	const Person p;
	p.showPerson();
	return 0;
}

插入一条:this指针的本质是 指针常量,其指向不可修改。

当成员函数在括号后面加上const后,就变为了常函数。

常函数不可以对一般的成员变量进行修改,但可以对加关键字mutable的变量修改。

(成员函数中往往都自带一个this指针)常函数中的this指针既不可修改指向也不可修改指向的值。

类似于常量指针常量。

常对象:

在创建对象时加一个 const 即变为常对象(例:const Person p)

常对象中的一般成员变量不可修改

class Person {
	public:
		int m_A;
		mutable int m_B;//特殊变量,常函数可以对其进行修改.
	public:
		void showPerson()const {
			//m_A = 500; 例如这个没有加关键字mutable 那么常函数就不能对其进行修改
			m_B = 500;
		}
};
int main() {
	const Person p;//常对象
	p.m_A = 50;//一般成员变量不能修改,会报错
	p.m_B = 60;//加关键字mutable的变量,不会报错。特殊变量。
	return 0;
}

例如:p.m_A=50会报错,p.m_B=60 不会报错。

还有在常对象中只能调用常函数,不能调用非 常函数。

因为在常函数中,只能修改mutable修饰的特殊变量,不能修改一般变量。

如果可以调用非 常函数的话,意味着将可在其中对一般变量进行修改,而这时不符合常函数和常对象的限制的。

class Person {
	public:
		int m_A;
		mutable int m_B;//特殊变量,常函数可以对其进行修改.
	public:
		void showPerson()const {
			m_B = 500;
		}
		void outPerson() {
			m_A = 666;
		}
};
int main() {
	const Person p;
	p.m_B = 60;
	return 0;
}

如上,常对象p 不能调用非 常函数 outPerson()。

综上,

常函数只能修改mutable修饰的变量,并且使函数中的this指针变为 常量指针常量

this指针本质为指针常量,this指针是被调用成员函数的对象的指针。

常对象只能调用常函数,不能调用非常函数。

常对象也可对被mutable修饰的变量进行修改。

友元

类内的私有属性在类外不能进行访问,但通过友元可以进行私有属性的访问。

其关键词为 friend。

全局函数做友元

class Building {
	friend void func(Building * building);
	public:
		string m_A;
	private:	
		string m_B;
	public:
		Building() {
			m_A = "客厅";
			m_B = "卧室";
		}
};

void func(Building *building){
	cout << "全局函数访问" << building->m_A << "\n";
	cout << "全局函数访问" << building->m_B << "\n";
}
int main() {
	Building building;
	func(&building);
	return 0;
}

全局函数 只能访问对象中的 公有变量 ,不能访问私有。

如果想让全局函数访问私有属性那么需要友元

    friend void func(Building * building);
在类中加上friend和全局函数的函数名

当加上friend后,利用友元 那么全局函数就能访问私有。

友元类

友元类顾名思义 用类以友元方式 去访问 另一个类中的私有属性

class Building {
	friend class Person;
	public:
		string m_A;
	private:	
		string m_B;
	public:
		Building() {
			m_A = "客厅";
			m_B = "卧室";
		}
};
class Person {
	public:
		string m_C;
	public:
		void func(Building *building) {
			cout << "友元类访问" << building->m_B<<"\n";
			m_C = building->m_B;
		}
};
int main(){
	Person p;
	Building building;
	p.func(&building);
	cout << p.m_C << "\n";
	return 0;
}

例如:用Person类去访问Building类中的私有属性,需要在Building类中加上 friend class Person;

表示友元类。

	void func(Building *building) {
			cout << "友元类访问" << building->m_B<<"\n";
			m_C = building->m_B;
		}

这里 便是 Person类 对Building类中的私有属性的访问。

插入一条:类内的成员函数在类外写。

class Person {
	public:
		int m_a;
	public:
		void func();
	public:
		Person(int a): m_a(a) { //初始化列表
		}
		Person() {
		}

};
void Person::func() {
	cout << m_a;
	cout << "\n";
}
int main() {
	Person p(10);
	p.func();
	return 0;
}

如此便是把Person类中的func函数拿到类外来写。

成员函数作友元

没有什么亮点,就是friend关键字后面加上 函数所在类的函数。

例如:friend void Person::test1();

如此便可用成员函数作友元。

#include<iostream>
#include<string>
using namespace std;
class Building;
class Person {
	public:
		Person();
		void test1();
		void test2();
	public:
		Building* building;
};
class Building {
	friend void Person::test1();
	public:
		string m_a;
	private:
		string m_b;
};
Person::Person() {
	building = new Building;
}
void Person::test1() {
	cout << "test1访问公有成员"<<building->m_a<<"\n";
	cout << "test1访问私有成员" << building->m_b<<"\n";
}
void Person::test2() {
	cout << "test2访问公有成员" << building->m_a << "\n";
	//cout << "访问私有成员" << building->m_b << "\n";
}
int main() {
	Person p;
	p.test1();
	p.test2();
	return 0;
}

上面用test1()和test2()作了比较,

test1()作友元,test2()未作友元。

他ma的 奇怪的是必须得把成员函数的函数内容写在类外否则不能访问私有成员

运算符重载

加号运算符重载

据我们所知,我们可以用int a,int b,然后直接用a+b来实现他们相加。 

成员函数实现加号运算符重载

但如果应用到 类的类型 中便不能直接用“➕”

此时成员函数 operator+ 便应运而生 从而直接用➕就可相加。

class Person {
	public:
		int m_a, m_b;
	public:
		Person operator+(Person &p) {
			Person temp;
			temp.m_a = this->m_a + p.m_a;
			temp.m_b = this->m_b + p.m_b;
			return temp;
		}
};
int main() {
	Person p1, p2;
	p1.m_a = p1.m_b = 10;
	p2.m_a = p2.m_b = 10;
	Person p3 = p1 + p2;
	cout << p3.m_a << " " << p3.m_b << "\n";
	system("pause");
}

注意成员函数叫做 operator+

为什么 Person operator+(Person &p)中用的是引用?

因为引用节省了空间。否则值传递将开辟新的内存来存放这个数据。

全局变量重载加号运算符

就是相当于把成员函数写倒了类外,变为了全局函数。

Person operator+(Person& p1, Person& p2) {
	Person p3;
	p3.m_a = p1.m_a + p2.m_a;
	p3.m_b = p1.m_b + p2.m_b;
	return p3;
}

注意:关于重载加号运算符的成员函数和全局函数只能有一个否则会报错。

还有就是关于:

重载加号运算符的函数重载

函数重载的条件:

  • 函数名称必须相同。
  • 参数列表必须不同(个数不同、类型不同、参数排列顺序不同等)。
  • 函数的返回类型可以相同也可以不相同。
  • 仅仅返回类型不同 不足以成为函数的重载。
Person operator+(Person& p1, Person& p2) {
	Person p3;
	p3.m_a =
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值