成都网站建设设计

将想法与焦点和您一起共享

C++基础-类与对象(1)

C++类与对象(1)

类的设计:可以把属性和行为放在不同的权限下

创新互联公司公司2013年成立,先为沧源等服务建站,沧源等地企业,进行企业商务咨询服务。为沧源企业网站制作PC+手机+微官网三网同步一站式服务解决您的所有建站问题。

struct和class区别在于某人的访问权限不同

  • struct:默认共有
  • class:默认私有

对象的初始化和清理

如果我们不写,系统会自己给我没写

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

没有返回值,也不写void

函数名和类型相同

可以有参,也可以无参

在调用对象会自动调用函数,无需手动调用,只调用一次

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

同上,不过是无参(不可以重载),销毁时自动调用函数,只调用一次

构造函数的分类与调用

分类

  • 有参和无参(默认)
  • 普通和拷贝

拷贝函数

函数名(const 函数名 &p(对象)){
    age = p.age;
}
#include           //构造函数和析构函数
using namespace std;
class person{
public:
	int age;
public:
	//构造函数
	//普通
	person(){
		cout << "Person无参构造函数的调用" << endl;
	}
	person(int a){
		age = a;
		cout << "Person有参构造函数的调用" << endl;
	}
	//拷贝
	person(const person &p){
		age = p.age;
		cout << "Person拷贝构造函数的调用" << endl;
	}
	//析构函数
	~person(){
		cout << "Person析构函数的调用" << endl;
	}
};

void test01(){
	//调用
	//1.括号法
	//person p1;
	//person p2(10);
	//person p3(p2);
	//注:调用默认构造函数时,不要加()(系统会以为是一个函数的声明)
	
	//cout << "p2的年龄:" << p2.age << endl;
	//cout << "p3的年龄:" << p3.age << endl;
	
	//2.显示法
	person p4;
	person p5 = person(10);//右值:匿名对象,当前行结束,系统收回匿名对象
	person p6 = person(p5);
	//注:不要利用拷贝函数初始化匿名对象
	
	
	//3.隐式转换法
	person p7 = 10;//相当于person p5 = person(10);
	person p8 = p7;
}

int main(){
	test01();
 return 0;
}*/

拷贝函构造函数的调用时机

c++中调用拷贝函数一般三种请况

  • 使用一个已经创建完毕的对象来初始化一个对象
  • 值传递的方法给函数参数传值
  • 以值方式返回局部对象
#include          //拷贝时机
using namespace std;

class Person{
private:
	int m_Age;	
public:	
	Person(){
		cout << "person的默认无参构造函数调用" << endl;
	}
	
	Person(int age){
		cout << "Person有参构造函数调用" << endl;
		m_Age = age;	
	}
	
	Person(const Person &p){
		cout << "Person的拷贝调用" << endl;
		m_Age =p.m_Age;	
	}
	
	~Person(){
		cout << "person的析构函数调用" << endl;
	}	
};

void test01(){
	Person p1 = Person(20);
	Person p2(p1);	
}

void dowork(Person p){	}
void test02(){
	Person p;
	dowork(p);	
}

Person dowork2(){
	Person p1;
	cout << (int*)&p1 << endl;  //输出地址
	return p1;
}
void test03(){
	Person p = dowork2();
	cout << (int*)&p << endl;	
}

int main(){
	test01();
    cout << endl;
	test02();
    cout << endl;
	test03();
 return 0;
}

构造函数的调用规则

默认情况下,C++会至少给一个类添加3个函数

  • 默认构造函数无参,函数体为空
  • 默认析构函数无参,函数体为空
  • 默认拷贝构造函数,对属性进行拷贝(所有的属性都进行赋值操作)

规则如下:

  • 如果用户定义了有参构造函数,C++不在提供默认无参构造,但会提供默认的拷贝
  • 如果用户定义了拷贝函数,C++不h会提供其他构造函数

注意:若只定义了拷贝(只有参同理),则

Person p1;
//和
Person p1(20);
//均是错误的(因为系统不会提供)

深拷贝和浅拷贝

浅拷贝:简单的赋值拷贝操作(如果对其进行释放,则堆区的内存会重复释放,出现错误)

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

m_height = new int(*P.m_height); //new返回的值是地址

private:
int * m_height;  //地址(指针)类型

初始化列表

作用:用来初始化属性

语法:

构造函数():属性1(值1),属性2(值2)...{}

来个浅例吧

#include 
using namespace std;

class Person{	
public:
	//传统初始化
//	Person(int a,int b,int c){
//		m_A = a;
//		m_B = b;
//		m_C = c;
//	}

    //初始化列表
	Person(int a,int b,int c):m_A(a),m_B(b),m_C(c){} //参数a,b,c可以自由的改变所赋的值
	
    int m_A;
    int m_B;
    int m_C;
};

void test01(){
	Person p(30,20,10);
	//Person p;
	cout << "m_A:" << p.m_A << endl;
	cout << "m_B:" << p.m_B << endl;
	cout << "m_C:" << p.m_C << endl;
}

int main(int argc, char** argv) {
	test01();	
 return 0;
}

结果是30 20 10

类对象作为类的成员

C++类中的成员可以是另一个类的对象,我们成该成员为对象成员

例如:

class A{};
class B{
    A a; 
}
//敲黑板(好老的梗...)
//先构造A的对象(即先构造其他类的对象),再构造B的对象
//析构的顺序是相反的,先析构本类,再析构其他类

代码的简单例子

#include 
using namespace std;
#include  //要用字符串呢

class Phone{ //类一
public:
	//品牌名字
	string m_Pname;
	
	Phone(string name){
		m_Pname = name;
	}
};

class Person{ //类二
public:
	//姓名
	string m_Name;
	//手机
	Phone m_Phone;  //类一作为类二的成员
	
	Person(string Name,string Pname):m_Name(Name),m_Phone(Pname){}
		                                      //相当于Phone m_Phone = Pname = Phone(Pname)(隐式转化)
};

void test01(){
	Person p("张三","华为");
	cout << p.m_Phone.m_Pname;
}

int main(int argc, char** argv) {
	test01();	
 return 0;
}

结果:华为

静态成员函数

静态成员变量就是加上const

静态成员分成:

  1. 静态成员变量
    • 所有对象共享一份数据
    • 再编译阶段分配内存
    • 类内声明,类外初始化
  2. 静态成员函数
    • 所有对象共享一个函数
    • 静态成员函数只能访问静态成员变量(函数体内无法区分普通变量是那个对象的成员)
    • 也是有访问权限的,private下在类外就访问不到
#include 
using namespace std;
class Person{
public:
	//静态成员函数
	void static func(){
		m_a = 100;//静态成员函数访问静态成员变量
		cout << "func的调用" << m_a << endl;
	}
	static int m_a;//类内声明类外初始化
};
int Person::m_a = 0;

//两种访问方式
void test01(){
    
	//通过对象访问
	Person p;
	p.func();
	
	//通过类名访问
	Person::func();
}

int main(int argc, char** argv) {
	test01();	
	return 0;
} 

结果

对象模型和this指针

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

  • 只有非静态成员变量才属于类的对象上面

  • 空对象占用一个字节

C++编译器会给每个空对象分配一个字节的空间(独一无二的内存地址),防止区分空对象占内存的位置

#include 
using namespace std;
class Person1{};
class Person2{
    int m_a;//非静态成员变量,属于类的对象上
	static int m_b;//静态成员变量,不属于类的对象上
	void test01(){}//非静态成员函数,属于类的对象上
	static void test02(){}//静态成员函数,不属于类的对象上  
};
int Person2::m_b = 0;

void test01(){//空对象所占用的内存
	Person1 p1;
	cout << "p1 sizeof of p is " << sizeof(p1) << endl;	
}

void test02(){//非空对象占用的内存
	Person2 p2;
	cout << "p2 sizeof of p is " << sizeof(p2) << endl;	
}
	
int main(int argc, char** argv) {
	test01();
	test02();
	return 0;
} 

this指针

引子:在上面我们知道,非静态的成员函数只会生成一份函数实例,也是是说多个同类的对象会公用一块代码(一个函数),那么:这一块代码是如何区分是那个对象调用自己呢?

通过this指针来解决上面的问题,this指针指向被调用的成员函数所属的对象(eg:p1调用就指向p1...)

  • this指针是隐含在每一个非静态成员函数内的一种指针,不用定义,直接使用

用途

  1. 当形参和成员变量重名时,可用this来区分
  2. 在类的非静态成员函数返回对象本身(return *this;)
#include 
using namespace std;
class Person1{//名称冲突
public:
	Person1(int age,int age1){
		this->age = age;
		age1 = age1;
	}
    
	int age;
	int age1;
	
	Person1 & Add(Person1 &p){
		this->age += p.age;
		//this是一个指向p3的指针,*this就是对象p3的本体
		return *this;	
	}
};

void test01(){
	Person1 p1(18,18);
	cout << "p1的年龄是" << p1.age << endl;
	cout << "p1的年龄是" << p1.age1 << endl;	
}
void test02(){//把p2的年龄加到p3上
	Person1 p2(10,10);
	Person1 p3(10,10);
	p3.Add(p2).Add(p2);//链式编程思想
	cout << "p3的年龄是" << p3.age << endl;
}

int main(int argc, char** argv) {
	test01();
	test02();
	return 0;
} 

结果

空指针访问成员函数

C++中允许空指针调用成员函数的,但是也要注意有没有用到this地址

如果用到this指针,则需要加以判断确保代码的健壮性

if(this == NULL) return;

看个小例子吧

#include 
using namespace std;
class Person{
public:
	void show(){
		cout << "show的调用" << endl;
	}
	
	int m_age;
	void get(){
		if(this == NULL) return;
		cout << "age=" << m_age << endl;
		              //默认this->m_age
	}
};


void test01(){
	Person * p = NULL;
	//空指针可以访问成员
    p->show();
	p->get();	
}
	
int main(int argc, char** argv) {
	test01();
	return 0;
} 

const修饰成员函数

常函数

  • 不可以修改成员属性
  • 成员属性声明时+mutable关键字,在常函数中就可以修改了
class Person{
public:
	//this本质 指针常量 Person * const this 指向不可以改变
	//在成员函数后面+const <=>const Person * const this,让指针指向的值不可以改变
	void show() const{ 
		m_a =100;//所以会报错哦
		//其实是this->m_a = 100;
	}
	int m_a;
	
};

常对象

  • 常对象只能调用常函数
const Person p;
p.show();

友元

在程序里,有些私有的属性想让类外的特殊的一些函数或者类进行调用,就需要友元技术

作用(目的):让一个函数或是类访问另一个类中的私有成员

友元关键字:friend(友元:不是类的成员,不受访问限制)

友元的三种实现

  • 全局函数友元
  • 类做友元
  • 成员函数做友元

全局函数做友元

#include 
using namespace std;
#include
class Building{
	//goodFriend是Building类的好朋友,可以访问啦
	friend void goodFriend(Building &building);
    
public:
	Building(){
		SittingRoom = "客厅";
		BedRoom = "卧室";
	}
public:
	string SittingRoom;//客厅	
private:
	string BedRoom;//卧室	
};

//全局函数
void goodFriend(Building &building){
	cout << "友元全局函数 正在访问:" << building.SittingRoom << endl;
	cout << "友元全局函数 正在访问:" << building.BedRoom << endl;
}

void test01(){
	Building building;
	goodFriend(building);
}
	
int main(int argc, char** argv) {
	test01();
	return 0;
} 


结果

类做友元

大致流程:

  • 先创建GoodFriend类的对象GF
  • 调用本类下的构造函数:创建一个Building(同时调用Building的构造函数)
  • 访问visit()函数,就可以访问building下的成员啦
#include 
using namespace std;
#include

class Building{
	//GoodFriend类是Building类的好朋友
	friend class GoodFriend;
    
...//和上面一样
};

class GoodFriend{
public:
	GoodFriend(){
		//创建对象
		building = new Building;
	}
    
	void visit(){//参观函数 访问Building中的属性
		cout << "友元正在访问:" << building->SittingRoom << endl;
		cout << "友元正在访问:" << building->BedRoom << endl;
	}
    
	Building * building;
};

void test01(){
	GoodFriend GF;
	GF.visit();
}
	
int main(int argc, char** argv) {
	test01();
	return 0;
} 


结果

成员函数做友元

流程与上面的几乎一样

#include 
using namespace std;
#include

class Building;//防止在未创建BUilding类是报错
class GoodFriend{
public:
    Building * building;
	GoodFriend();
	void visit();//参观函数 访问Building中的私有成员
};

class Building{
    //visit()做为BUilding类的好朋友
	friend void GoodFriend::visit();
    
public:
	string SittingRoom;//客厅
private:
	string BedRoom;//卧室
    
public:
	Building();
};

//类外声明
Building::Building(){
	SittingRoom = "客厅";
	BedRoom = "卧室";
}

GoodFriend::GoodFriend(){
	building = new Building;
}
void GoodFriend::visit(){//参观函数 访问Building中的私有成员
	cout << "友元正在访问:" << building->SittingRoom << endl;
	cout << "友元正在访问:" << building->BedRoom << endl;
}

void test01(){ //测试函数
	GoodFriend GF;
	GF.visit();	
}
	
int main(int argc, char** argv) {
	test01();
	return 0;
} 


结果:

运算符的重载

概念:对已有运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型

对于内置的数据类型,系统知道如何进行运算

加号运算符重载(其他同理)

  1. 成员函数重载+号

本质:Person p3 = p1.operator+(p2);

#include 
using namespace std;
class Person{
public:
	int m_A;
	int m_B;
/*======================================================*/
	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;
	}
/*======================================================*/
};

void test01(){
	Person p1;
	p1.m_A = 10;
	p1.m_B = 10;
	Person p2;
	p2.m_A = 10;
	p2.m_B = 10;
	
	Person p3 = p1 + p2;
	
	cout << p3.m_A <

结果是两个20(相加成功)

  1. 全局函数重载+号

本质:Person p3 = operator+(p1,p2);

#include 
using namespace std;
class Person{
public:
	int m_A;
	int m_B;	
};
/*======================================================*/
Person operator+(Person &p1,Person &p2){
	Person temp;
	temp.m_A = p1.m_A + p2.m_A;
	temp.m_B = p1.m_B + p2.m_B;
	return temp;
}
/*======================================================*/
int main(int argc, char** argv) { //函数和上面的一样
	test01();	
 return 0;
}

结果也是两个20

注意:- 运算符的重载也可以发生函数重载(名字相同,参数不同)

       - 不可以改变内置运算符

左移运算符的重载(<<)

只能利用全局函数重载左移运算符

作用:输出自定义的数据类型

本质:operator<<(cout,p) => cout << p

#include 
using namespace std;
class Person{
public:
	int m_A;
	int m_B;
};
/*======================================================*/
ostream & operator<<(ostream &out,Person &p){
	out << "m_A:" << p.m_A << '\t' << "m_B:" << p.m_B << endl;
	return out;
}
/*======================================================*/
void test01(){
	Person p;
	p.m_A = 10;
	p.m_B = 10;
	cout << p << endl;
}

int main(int argc, char** argv) {
	test01();	
 return 0;
}

结果:m_A:10 m_B:10

类的成员变成私有:用友元

class Person{
     friend ostream & operator<<(ostream &out,Person &p);
public:
	int m_A;
	int m_B;
};

递增运算符重载(++)

前置返回引用,后置返回值

  • 前置递增
 #include 
 using namespace std;
 class MyInteger{//自定义的整型
   friend ostream & operator<<(ostream &out,MyInteger &p);
 public:
   MyInteger(){
     m_Num = 0;
   }
   /*======================================================*/
   MyInteger & operator++(){//返回引用是为了一直对一个数据操作
     m_Num++;
     return *this;
   }
   /*======================================================*/
 private:
   int m_Num;
 };

 ostream & operator<<(ostream &out,MyInteger &p){
   out << p.m_Num;
   return out;
 }

 void test01(){
   MyInteger myint;
   cout << "myint:" << ++(++myint) << endl;
   cout << "myint:" << myint << endl;
 }

 int main(int argc, char** argv) {
   test01();  
 return 0;
 } 

结果1:myint:2(换行)myint:2

  • 后置递增
#include 
using namespace std;
class MyInteger{//自定义的整型
	friend ostream & operator<<(ostream &out,const MyInteger &p);
public:
	MyInteger(){
		m_Num = 0;
	}  
	/*======================================================*/
	MyInteger  operator++(int){//int 代表占位参数,可以用于区分前置和后置递增
		//先记录
		MyInteger temp = *this;
		//后递增
		m_Num++;
		//再返回
		return temp;
	}
	/*======================================================*/
private:
	int m_Num;
};
ostream & operator<<(ostream &out, const MyInteger &p){//这里加了const,否则在test02()的输出会有问题
	out << p.m_Num;
	return out;
}

void test02(){
	MyInteger myint;
	cout << "myint:" << myint++ << endl;
	cout << "myint:" << myint << endl;
}

int main(int argc, char** argv) {
	test02();
	return 0;
} 

结果2:myint:0(换行)myint:1

赋值运算符重载

补充构造函数调用规则,一个类至少4个函数

  • 第四个:赋值运算符operator=,对属性进行拷贝

p2 = p1的问题:堆区重复释放,和浅拷贝的问题是一样的

#include 
using namespace std;
class Person{
public:
	int *m_age;//开辟到堆区
	
	Person(int age){
		m_age = new int(age);
	}
    
	~Person(){
		if(m_age != NULL){
			delete m_age;
			m_age = NULL;
		}	
	}
    /*======================================================*/
    Person & operator=(Person &p){//和深拷贝几乎是一样的,返回值是引用是要满足连等
		//先判断左值是否有属性在堆区,如果有,先释放干净,再深拷贝
		if(m_age != NULL){
			delete m_age;
			m_age = NULL;		
		} 
		m_age = new int(*p.m_age);
		//返回对象本身
		return *this;
	}
};
/*======================================================*/
void test01(){
	Person p1(10);
	Person p2(20);
	Person p3(30);
	p3 = p2 = p1;//赋值操作
	cout << "p1的年龄是:" << *p1.m_age << endl;
	cout << "p2的年龄是:" << *p2.m_age << endl;
	cout << "p3的年龄是:" << *p3.m_age << endl;
}

int main(int argc, char** argv) {
	test01();
	return 0;
} 


结果:p1,p2,p3都是10

关系运算符的重载(>/<...)

  • ==的重载(!=同理)
#include 
using namespace std;
class Person{
public:
	string m_name;
	int m_age;
	
	Person(string name,int age){
		m_name = name;
		m_age = age;
	}
	~Person(){}
	/*======================================================*/
	bool operator==(Person &p){
		if(this->m_name == p.m_name&&this->m_age == p.m_age){
			return true;	
		}else{
			return false;
		}
	}
    /*======================================================*/
};
void test01(){
	Person p1("Tom",18);
	Person p2("Tom",18);
	if(p1 == p2) cout << "p1和p2相等" << endl;
	else cout << "p1和p2不相等" << endl;
}
	
int main(int argc, char** argv) {
	test01();
	return 0;
} 

结果:p1和p2相等

函数调用运算符重载

  • 函数调用运算符()也可以重载
  • 由于重载后使用的方式非常像函数的调用,因此也称谓仿函数
  • 仿函数没有固定的写法,很灵活
#include //写了两个...
using namespace std;
#include
class Myprint{//打印类
public:
    /*======================================================*/
	void operator()(string test){
		cout << test <

结果:hello word(换行)3(换行)2


名称栏目:C++基础-类与对象(1)
网址分享:http://chengdu.cdxwcx.cn/article/dsoihoo.html