C++笔记(4)类和对象

4.1封装

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

类中的属性和行为统称为成员

              属性:成员属性 成员变量

              行为:成员函数 成员方法

封装的权限控制

公共权限 public:      成员  类内可以访问 类外也可以访问

保护权限 protected:成员  类内可以访问 类外不可以访问(儿子可以访问父亲的保护内容)

私有权限 private:     成员  类内可以访问 类外不可以访问(儿子不可以访问父亲的私有内容)

struct和class的区别:默认的访问权限不同

struct默认权限为公共,class默认权限为私有

Bool issame(Cube c1,Cube c2)//这里是值传递,会拷贝出一份数据,最好用引用传递,这样就会直接用原数据。 可以改成 bool issame(Cube &c1,Cube &c2)

bool的结果包括true false

4.2.1对象的初始化和清理(构造函数和析构函数)//没有返回值,不用写void

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

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

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

          (析构函数) ~类名(){}     //不可以有参数,不会发生重载

4.2.2构造函数的分类和调用

  • 分类:

1.按照参数分类    有参构造无参构造(默认构造)

class Person{

public:

Person()

{

Cout<<“Person的无参构造函数调用”<<endl;

}

Person(int a)

{

age = a;

Cout<<"Person的有参构造函数调用“<<endl;

}

private:

Int age;

};

2.按照类型分类   普通构造拷贝构造

Person(const Person &p)//加const是因为只需要拷贝,不能把原有的信息修改掉

{

age = p. age;//将传入的人身上的所有属性拷贝到该属性上。

}

  • 调用

1.括号法

Person p1;//默认构造函数调用

Person p2(10);//有参构造函数调用

Person p3(p2);//拷贝构造函数调用

注意事项:

调用默认构造函数时不要加(),因为对下面这行代码,编译器会认为是一个函数的声明,不会认为在创建对象

Person p1();                          void func();

2.显示法

Person p1;//无参构造函数调用

Person p2 = Person(10);//有参构造函数调用

Person p3 = Person(p2);//拷贝构造函数调用

Person(10);//是个匿名对象,但放在等号右边时,等号左边就相当于对象名

匿名对象的特点:当前行执行结束以后系统会立即回收掉匿名对象(因为没有对象名,后续无法使用该对象)

注意事项:

不要利用拷贝构造函数来初始化匿名对象(下面的p3会调用拷贝构造函数)

例:Person (p3);//编译器会认为Personp3==Person p3,报错Person p3重定义。是个对象的声明。

3.隐式转换法

Person p4 = 10;//相当于Person p4 = Person(10);有参构造函数调用

Person p5 = p4;//拷贝构造函数调用

4.2.3拷贝构造函数调用时机

class Person{

public:

Person(){ cout<<"无参构造函数的调用"<<endl;}

Person(int age){

m_age = age;

cout<<"有参构造函数的调用"<<endl;

}

Person(const Person &p){

m_age = p.age;

cout<<"拷贝构造函数的调用"<<endl;

}

~Person();

private:

int m_age;

};

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

void test01(){

Person p1(20);

Person p2(p1);

cout<<"p2的年龄为:"<<p2.m_age<<endl;   //输出结果为“p2的年龄为:20”

}

  • 以值传递的方式给函数参数传值

void dowork(Person p){ }

void test02(){

Person p;//会调用无参构造函数

dowork(p);//会调用拷贝构造函数,但是如果在dowork内修改成员信息,对对象p无影响,因为调用时生成拷贝构造函数,修改的是拷贝的对象

}

  • 以值方式返回局部对象

Person dowork2(){

Person p1;

return p1;//返回的不是p1,而是一个p1的拷贝对象

}

void test03(){

Person p = dowork2();

}

4.2.4 构造函数的调用规则

  • 如果用户定义有参构造函数,c++不再提供默认无参构造,但会提供默认拷贝构造

不能这样定义对象,例如Person p

  • 如果用户定义拷贝构造函数,c++不会再提供其他构造函数

创建一个类,c++编译器会给每个类都添加至少三个函数:默认构造(空实现),析构函数(空实现),拷贝构造(值拷贝)。

例:Person p;

p.age = 18;

Person p2(p);//前提:类中没有写拷贝构造函数

cout<<"P2的年龄为:"<<p2.age<<endl;//输出结果仍为18,编译器提供了拷贝构造函数进行值传递

4.2.5深拷贝与浅拷贝

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

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

class Person{

public:

Person(){

cout<<"Person的默认构造函数调用"<<endl;

}

Person(int age,int height){

m_age = age;

m_height = new int(height);//用new把height创建在堆区,返回的是int*类型

//堆区开辟的数据由程序员手动开辟手动释放,所以在对象销毁前应释放。在析构函数中释放

cout<<"Person的有参构造函数调用"<<endl;

}

~Person(){

//析构函数,将堆区开辟数据做释放操作

if(m_height !=NULL){

delete m_height;

m_height = NULL;//防止野指针出现

}

cout<<"Person的析构函数调用"<<endl;

}

private:

int m_age;

int *m_height;//int 类型的指针,指向身高

};

void test01(){

Person p1(18,160);

cout<<"p1的年龄为"<<p1.age<<"身高为"<<*p1.m_height<<endl; 

 //如果调用p1.m_height则输出的是地址,用*m_height则输出身高

Person p2(p1);

//利用编译器提供的拷贝构造函数会做浅拷贝操作,堆区的内存会被重复释放(p2已经调用析构函数释放掉堆区内存,p1会再次调用析构函数释放)

cout<<"p2的年龄为"<<p2.age<<"身高为"<<*p2.m_height<<endl;

}

解决方法:自己实现拷贝构造函数,重新在堆区创建一块内存,解决浅拷贝带来的问题

Person(const Person &p){

cout << "Person的拷贝构造函数调用" <<endl;

m_age = p.m_age ;

//m_height = p.m_height ; 编译器默认实现就是这行代码

//深拷贝操作

m_height = new int*p.m_height);

}

4.2.6初始化列表

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

Person(int a,int b,int c):m_a(a),m_b(b),m_c(c){}

4.2.7类对象作为类成员

class Phone{

public:

Phone(string pName){

m_PName = pName;

}

string m_PName;

};

class Person{

public:

Person(string name, string pName):m_Name(name),m_Phone(pName){ }

string m_Name;

Phone  m_Phone;

};

当其他类对象作为本类成员,构造时候先构造其他类对象,再构造自身。

析构的顺序与构造时相反。

4.2.8静态成员函数

  • 所有对象共享同一个函数
  • 静态成员函数只能访问静态成员变量

class Person{

public:

static void func(){

m_A = 100;//静态成员函数可以访问静态成员变量

m_B = 200//静态成员函数不可以访问非静态成员变量(m_B属于特定对象的成员,函数体无法区分到底是哪一个对象的,所以不能访问修改

cout<<" static void func 的调用"<<endl;

}

static int m_A;

int m_B;

private:

static void func2(){}//静态成员函数也有访问权限,类外访问不到func2函数。

};

int Person::m_A = 0;

void test01(){

1.通过对象访问

Person p;

p.func();

2.通过类名访问

Person::func();//所有对象共享该函数,所以不用创建对象也可以访问该函数

}

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

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

空对象占用内存空间为1,c++编译器也会给每个空对象分配一个字节的空间,是为了区分空对象占内存的位置。

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

class Person{

int m_A;//非静态成员变量 属于类的对象上

static int m_B;//静态成员变量 不属于类的对象上

void func(){}//非静态成员函数 不属于类的对象上

static void func(){}//静态成员函数 不属于类的对象上

};//该对象占用内存空间为4个字节,仅为int类型变量内存大小

4.3.2 this 指针概念

this 指针指向被调用的成员函数所属的对象,隐含在每一个非静态成员函数内

1.解决名称冲突

class Person{

public:

Person(int age){

this->age = age;//this指针指向被调用的成员函数所属的对象

}

int age;

};

2.返回对象本身用*this

class Person{

public:

Person(int age){

this-> age = age;

}

Person & PersonAddAge(Person &p)

 //如果不是返回引用而是返回值(Person PersonAddAge(Person &p)),那最后输出结果为20

//如果是值,则会调用拷贝构造函数,创建新的对象

{

this->age += p.age;

return *this; //this指向p2的指针,而*this指向的就是p2这个对象本体

}

int age;

};

void test(){

Person p1(10);

Person p2(10);

//链式编程思想

p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);

cout<<"p2的年龄为:"<<p2.age<<endl;//输出结果为40

4.3.4 const 修饰成员函数(const是为了 限定只读状态)

常函数:

  • 成员函数后加const则称这个函数为常函数
  • 常函数内不可以修改成员属性
  • 成员属性声明时加关键字mutable后,在常函数中依然可以修改

class Person{

public:

//this指针的本质是指针常量,指针的指向是不可以修改的

//相当于Person * const this;

//在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改

//相当于const Person * const this;

void showPerson()const

{

this->m_A = 100;//报错,指针指向的值不能修改

m_A = 100;//同上

this->m_B = 100;//不报错

}

int m_A;

mutable int m_B;//特殊变量,即使在常函数中也可以修改

};

常对象:

  • 声明对象前加const称该对象为常对象
  • 常对象只能调用常函数

const Person p;//在对象前加const,变为常对象

p.m_A = 100;//报错

p.m_B = 100;//不报错,特殊值,即使在常函数下也可以修改

4.4 友元

4.4.1全局函数作友元

假设有一全局函数 void myfriend(Building &building)//里面要访问到类中的私有成员属性

则在类中可以先声明 friend void myfriend(Building &building);

//该全局函数是类Building的好朋友,可以访问类中的私有成员

4.4.2 类作友元

如果有一类myfriend要访问类Building中的私有成员,则在Building中写friend class myfriend即可

//该类是Building类的好朋友,可以访问Building类中的私有内容

4.4.3成员函数作友元

如果myfriend类下的visit函数想要访问Building类中的私有成员,而visit2函数不可以访问,则在Building类中声明friend void myfriend::visit();即可

//告诉编译器,myfriend类下的visit成员函数作为本类的好朋友,可以访问类中的私有内容

4.5运算符重载

4.5.1 加号运算符重载

  • 通过成员函数重载+

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;

}

在主函数中就可以运行Person p3 = p1.operator+(p2);//标注出的部分为函数名

可以简化为Person p3 = p1 + p2;

  • 通过全局函数重载+

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;

}

在主函数中就可以运行Person p3 = operator+(p1,p2);

可以简化为Person p3 = p1+p2;

运算符重载也可以发生函数重载

4.5.2 左移运算符重载

利用成员函数重载左移运算符 p.operator<<(cout) 简化为p<<cout

通常不会利用成员函数重载<<运算符,因为无法实现cout在左侧,所以只能利用全局函数重载左移运算符

void operator<<(ostream &cout ,Person &p){

cout<<"m_A = "<<p.m_A<<" m_B ="<<p.m_B;

}//ostream是输出流类

在主函数中就可以写cout<<p;

如果想调用cout<<p<<endl;利用链式思想把子函数改为

ostream &operator<<(ostream &cout,Person &p){

cout<<"m_A = "<<p.m_A<<" m_B ="<<p.m_B;

return cout;

}

再利用友元函数访问私有成员即可。

4.5.3 递增运算符重载    ++

  • 重载前置++运算符

MyInteger& operator++(){

m_Num++;//先对自身属性做++运算

return *this;//再对自身做返回

}//返回引用是为了一直对一个数据进行递增操作,如果不是返回引用,则++++myint)后再输出myint,值仍为++myint的值

  • 重载后置++运算符

MyInteger operator++(int)//int代表占位参数,可以用于区分前置和后置递增

{

//先记录当时结果

MyInteger temp = *this;

//后递增

m_Num ++;

//最后将记录结果返回

return temp;

}//返回的是值,如果返回引用则返回的是局部对象,函数运行完以后就会被释放,后续操作则为非法操作

4.5.4 赋值运算符重载

class Person{

public:

Person(int age)

{

m_age = new int(age);

}

~Person(){

if(m_age!=NULL)

delete m_age;

m_age = NULL;

}

}

int *m_age;

};//如果用系统定义的赋值运算符完成p2=p1操作,则堆区内存会被重复释放,所以要重载赋值运算符

Person& operator=(Person &p){

//编译器提供的是浅拷贝,m_age = p.age;

//应该先判断是否有属性在堆区,如果有,先释放干净,再深拷贝

if(m_age!=NULL){

delete m_age;

m_age=NULL;

}

m_age = new int(*p.m_age);

//返回对象本身

return *this;

}

4.5.5关系运算符重载

bool operator==(Person &p){

if(this->m_Name == p.m_Name&& this->m_age == p.m_age)

return true;

else return false;

}

bool operator !=(Person &p){

if(this->m_Name == p.m_Name&& this->m_age == p.m_age)

return false;

else return true  ;

}

int main(){

Person p1("Tom",18);

Person p2("Tom",18);

if(p1 == p2)

cout<<"p1和p2是相等的!"<<endl;

if(p1 != p2)

cout<<"p1和p2是不相等的!"<<endl;

4.5.6 函数调用运算符重载

class Print{

public:

void operator()(string test){

cout<<test<<endl;

}

};

int main(){

Print myprint;

myprint("Hello World!");//由于使用起来非常类似函数调用,因此称为仿函数

}

class MyAdd{

public:

int operator()(int num1,int num2){

return num1+num2;

}

};

int main(){

MyAdd myadd;

int ret = myadd(100,100);

cout<<ret<<endl;

//匿名函数对象调用

cout<<MyAdd()(100,100)<<endl;

}

4.6 继承

语法:

class 子类:继承方式 父类//子类也成为派生类,父类也成为基类

4.6.3继承中的对象模型

在父类中所有非静态成员属性都会被子类继承

父类中私有成员属性是被编译器隐藏了,访问不到,但是会被继承

利用开发人员命令提示工具查看对象模型

//跳转盘符    F

//跳转文件路径    cd   具体路径下

//查看命名

//cl /d1 reportSingleClassLayout类名  文件名

4.6.4继承中的构造和析构顺序

先构造父类,再构造子类,析构顺序和构造顺序相反

4.6.5继承同名成员的处理方式(Son s;)

  • 访问子类同名成员,直接访问即可   例  s.m_a     s.func();
  • 访问父类同名成员,需要加作用域  例  s.Base::m_a    s.Base::func();

//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数

//如果像访问到父类中被隐藏的同名成员函数,需要加作用域

4.6.6 继承中同名静态成员的处理假设子类和基类中均有成员static int m_a

通过对象访问,与上面一样

通过类名访问   Son::m_a(子类) Base::m_a或Son::Base::m_a(父类)

                                                                       //第一个::代表通过类名方式访问,第二个::表示访问父类作用域下

4.6.7 多继承语法//实际开发中不建议用

class 子类:继承方式 基类1,继承方式 基类2……

//当父类中出现同名成员,需要加作用域区分

例如Base1和Base2中同时含有m_A

则输出时通过s.Base1::m_A 和 s.Base2::m_A 来区分

4.6.8 菱形继承

利用虚继承可以解决菱形继承的问题

Sheep 和 Tuo类在继承类型前加上virtual 变为虚继承

Animal类称为虚基类

从Sheep和Tuo继承下来的是vbptr指针(虚基类指针)指向虚基类的成员

4.7.1多态的基本概念

  • 静态多态:函数重载和运算符重载属于静态多态,复用函数名(函数地址在编译阶段确定)
  • 动态多态:派生类和虚函数实现运行时多态(运行阶段确定函数地址)

动态多态的满足条件:

1.有继承关系

2.子类要重写(函数返回值类型、函数名、参数列表完全相同)父类中的虚函数

动态多态使用

父类的指针或引用 指向子类对象

4.7.3纯虚函数和抽象类

virtual 返回值类型 函数名(参数列表)=0;

当类中只要有一个纯虚函数,这个类就是抽象类

抽象类的特点:

1.无法实例化对象 Base b或new Base 都无法运行

2.抽象类的子类必须要重写父类中的纯虚函数,否则也属于抽象类

4.7.5虚析构和纯虚析构

多态在使用时,如果子类中有属性开辟到堆去,那么父类指针在释放时无法调用到子类的析构代码,

解决方式:将父类的析构函数改为虚析构或者是纯虚析构

纯虚析构需要声明也需要实现,纯虚析构的实现在类外写。

有了纯虚析构以后这个类成为抽象类

如果子类中没有堆区数据,可以不写虚析构或者纯虚析构

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值