2 类和对象

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/FAKER_0X0/article/details/89406387

类和对象

#知识点总结#

面向对象三大概念: 封装、继承、多态

1类和对象

类的封装
有2层含义(把属性和方法进行封装 对属性和方法进行访问控制)

在用struct定义类时,所有成员的默认属性为public
在用class定义类时,所有成员的默认属性为private

#pragma once //只包含一次 和条件编译的作用是一样的。

类的声明和类的实现分开

2对象的构造和析构

类的数据成员是不能在声明类时初始化的。

2.1构造和析构函数

C++编译器提供了构造函数(constructor)来处理对象的初始化。
与类名相同的成员函数叫做构造函数;构造函数在定义时可以有参数
自动调用:一般情况下C++编译器会自动调用构造函数
手动调用:在一些情况下则需要手工调用构造函数

定义一个特殊的成员函数清理对象,这个特殊的成员函数叫做析构函数
语法:~ClassName()
析构函数没有参数;在对象销毁时自动被调用

构造函数先创建的对象后释放

C++编译器构造析构方案 PK 对象显示初始化方案
定义对象数组时,没有机会进行显示初始化

2.2构造函数的分类及调用

1.无参数构造函数 调用方法: Test t1, t2;
2.有参构造函数 三种调用方法:
Test5 t1(10); 括号法
Test5 t2 = (20, 10); 等号法
Test5 t3 = Test5(30); 直接调用构造函数法

直接调用构造函数会产生一个匿名对象(难点:匿名对象的去和留)

3.拷贝构造函数调用时机
目的:完成对象的初始化;C++编译器自动调用
AA(const AA &obj2)
{
a = obj2.a + 10;
}
Const可加可不加

拷贝构造函数的四种调用场景:
(1)(2)用对象1 初始化 对象2
AA a2 = a1; 初始化法
AA a2(a1); 括号法
注意:对象初始化操作 和 等号操作 是两个不同的概念
(3)复制对象作为参数传递给函数
void f ( Location p )
{
cout << “Funtion:” << p.GetX() << “,” << p.GetY() << endl ;
}
void mainobjplay()
{
Location A ( 1, 2 ) ; //形参p是一个元素,函数调用,会执行实参变量A初始化形 参变量p
f ( A ) ;
}
(4)
g函数返回一个元素
结论1:函数的返回值是一个元素(复杂类型的),返回的是一个新对象类(匿名对象)的拷贝构造函数。
匿名对象的去和留,关键看,返回时如何接:
若返回的匿名对象,赋值给另外一个同类型的对象,那么匿名对象会被析构
若返回的匿名对象,来初始化另外一个同类型的对象,那么匿名对象会直接转成新的对象

4.默认构造函数
默认无参构造函数:其函数体为空
默认拷贝构造函数:简单的进行成员变量的值复制

2.3构造函数调用规则研究

当类中定义了拷贝构造函数时,c++编译器不会提供无参数构造函数。
当类中定义了任意的非拷贝构造函数,c++编译器不会提供默认无参构造函数
总结:在定义类时,只要你写了构造函数,那么你必须用。

2.4深拷贝和浅拷贝

对象的数据资源是由指针指示的堆时,默认拷贝构造函数仅作指针值复制

如下图所示,浅拷贝指两个同类对象的指针变量指向堆的同一块内存,当obj2被析构后堆上的内存被释放,等到obj1被析构时,堆上的同一块内存又要被释放,同一块内存被析构两次会引起程序的coredump。

两个对象的成员使用同一块内存肯定容易出现问题
解决方案:手工编写拷贝构造函数,使用深拷贝(重新分配内存并复制相关内容到内存里)

C++编译器提供的 等号操作 也属于浅拷贝
解决方案:需要重载操作符

2.5多个对象构造和析构

对象初始化列表
出现原因:
1.必须这样做:
如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成员它只有一个带参数的构造函数,没有默认构造函数。这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数。
2、类成员中若有const修饰,必须在对象初始化的时候,给const int m 赋值
当类成员中含有一个const对象时,或者是一个引用时,他们也必须要通过成员初始化列表进行初始化,因为这两种对象要在声明后马上初始化,而在构造函数中,做的是对他们的赋值,这样是不被允许的。

Constructor::Contructor() : m1(v1), m2(v1,v2), m3(v3)
例 B(int _b1,int _b2,int m,int n): a1(m),a2(n),c(0)

注意:
成员变量的初始化顺序与声明的顺序相关,与在初始化列表中的顺序无关
初始化列表先于构造函数的函数体执行

2.6构造函数和析构函数的调用顺序研究

1)当类中有成员变量是其它类的对象时,首先调用成员变量的构造函数,调用顺序与 声明顺序相同;之后调用自身类的构造函数
2)析构函数的调用顺序与对应的构造函数调用顺序相反

2.7构造函数和析构函数综合练习

ABCD(400, 500, 600); 匿名对象生命周期
//会产生一个临时的匿名对象
//匿名的临时对象,编译器会立刻销毁,不等到正常函数调用完毕
ABCD abcd = ABCD(100, 200, 300); 匿名对象的去和留

直接调用构造函数会产生一个匿名对象
构造函数中调用构造函数(会产生匿名对象)是危险的行为。

2.8对象的动态建立和释放

C语言中是利用库函数malloc和free来分配和撤销内存空间的
注意: new和delete是运算符,不是函数,因此执行效率高
虽然为了与C语言兼容,C++仍保留malloc和free函数,但建议用户不用malloc和free函数,而用new和delete运算符。

用new分配数组空间时不能指定初值。如果由于内存不足等原因而无法正常分配空间,则new会返回一个空指针NULL,用户可以根据该指针的值判断分配空间是否成功。

使用类名定义的对象都是静态的,程序运行过程中,对象所占的空间是不能随时释放的。可以用new运算符动态建立对象,用delete运算符撤销对象
new能自动执行类的构造函数,delete能自动执行类的析构函数

new 在堆上分配内存 delete :
分配基础类型 、分配数组类型、分配对象

new和malloc 深入分析:
混用测试、异同比较 :可以混合调用
结论: malloc不会调用类的构造函数
free不会调用类的析构函数

3静态成员变量&成员函数

静态成员变量
把一个类的成员说明为 static 时,这个类无论有多少个对象被创建,这些对象共享这个 static 成员。
静态成员变量需要在类外初始化,int Counter :: smem = 1 ;
访问静态成员变量的2种方法:Counter :: smem ;c.smem;

静态成员函数
静态成员函数提供不依赖于类数据结构的共同操作,它没有this指针。
在类外调用静态成员函数用 “类名 :: ”作限定词,或通过对象调用。
静态成员函数中,不能使用普通变量。

4 C++面向对象模型初探

1)C++类对象中的成员变量和成员函数是分开存储的
成员变量:
普通成员变量:存储于对象中,与struct变量有相同的内存布局和字节对齐方式
静态成员变量:存储于全局数据区中
成员函数:存储于代码段中。

2)C++编译器对普通成员函数的内部处理
C++中类的普通成员函数都隐式包含一个指向当前对象的this指针。

this指针
若类成员函数的形参和类的属性名字相同,通过this指针来解决,否则成员变量的值为乱码。
Test(int m_k)
{
m_k = m_k; 应为this->m_k = m_k;
m_q = 0;
}

在成员函数后面加const 修饰的不是函数,修饰的是隐藏的this指针,修饰的是this指针指向的内存空间
本身this指针就是一个常指针,不可更改
int getK(int a,int b) const {…}
转换为全局函数:int getK(const Test *const this,int a,int b) {…}

全局函数PK成员函数
把全局函数转化成成员函数,通过this指针隐藏左操作数
Test add(Test &t1, Test &t2)===》Test add( Test &t2) 即t1.add(t2)调用
把成员函数转换成全局函数,多了一个参数,即this指针
void printAB()–》void printAB(Test *pthis)
函数返回元素和返回引用
Test& add(Test &t2) //*this //函数返回引用 实际实现t1=t1+t2
{
this->a = this->a + t2.getA();
this->b = this->b + t2.getB();
return *this; //操作让this指针回到元素状态 即(&t1)=t1,返回的是元素
}
Test add2(Test &t2) //*this //函数返回元素
{
//t3是局部变量 实际实现t3=t1+t2
Test t3(this->a+t2.getA(), this->b + t2.getB()) ;
return t3;
}

5 友元函数和友元类

其会破坏类的封装性。
为什么要设计?因为有时候确实要访问某些类的私有属性,就开了个后门,方便程序员调用。
友元函数:
friend void setA1(A1 *p, int a1);
说明语句位置与访问描述无关(在public和private里都一样)
友元类:

若B类是A类的友员类,则B类的所有成员函数都是A类的友员函数
友员类通常设计为一种对数据操作或类之间传递消息的辅助类

6 运算符重载

6.1概念

所谓重载,就是重新赋予新的含义。
编译器给提供了一种机制,让用户自己去完成,自定义类型的加减操作这个机制就是运算符重载机制。
运算符重载的本质是一个函数

6.2运算符重载的限制

6.3运算符重载编程基础

运算符重载的两种方法:

通过类成员函数完成
通过全局函数方法完成

前置—操作符
Complex& operator–()
后置— 操作符
Complex operator–(int) ////先使用 后++
前置和后置运算符总结
C++中通过一个占位参数来区分前置运算和后置运算

全局函数、类成员函数方法实现运算符重载步骤
1)要承认操作符重载是一个函数,写出函数名称operator+ ()
2)根据操作数,写出函数参数
3)根据业务,完善函数返回值(看函数是返回引用 还是指针或者元素),及实现函数业 务
友元函数实现操作符重载的应用场景
1)友元函数和成员函数选择方法
当无法修改左操作数的类时,使用全局函数进行重载
=, [], ()和->操作符只能通过成员函数进行重载

2)只能用友元函数 重载 << >>操作符
istream 和 ostream 是 C++ 的预定义流类
cin 是 istream 的对象,cout 是 ostream 的对象
ostream& operator<<(ostream &out, Complex &c1)

//调用方法
cout<<c1;
//链式编程支持
cout<<c1<<“abcc”;
cout.operator<<(c1).operator<<(“abcd”);
//函数返回值充当左值 需要返回一个引用

类成员函数方法无法实现 << 操作符重载
//因无法拿到cout这个类的源码
cout.operator<<(c1);

3)友元函数重载操作符使用注意点
友员函数重载运算符常用于运算符的左右操作数类型不同的情况

6.4运算符重载提高

1.重载赋值运算符=
赋值运算符重载用于对象数据的复制
重载函数原型为:
类型 & 类名 :: operator= ( const 类名 & ) ;
结论:
1 先释放旧的内存
2 返回一个引用
3 =操作符 从右向左

2.重载数组下标运算符[]
运算符 [] 和 () 是二元运算符
[] 运算符用于访问数据对象的元素
重载格式 类型 类 :: operator[] ( 类型 ) ;
设 x 是类 X 的一个对象,则表达式
x [ y ]
可被解释为
x . operator [ ] ( y )
函数返回值当左值时需要返回一个引用。

3.重载函数调用符 ()
设 x 是类 X 的一个对象,则表达式
x ( arg1, arg2, … )
可被解释为
x . operator () (arg1, arg2, … )
例:
double F :: operator ( ) ( double x , double y )
{ return x * x + y * y ; }
F f ;
f ( 5.2 , 2.5 );

5.为什么不要重载&&和||操作符
1)&&和||是C++中非常特殊的操作符
2)&&和||内置实现了短路规则
3)操作符重载是靠函数重载来完成的
4)操作数作为函数参数传递
5)C++的函数参数都会被求值,无法实现短路规则

Test t1 = 0;
Test t2 = 1;
if ( t1 && (t1 + t2) ) //两个函数都被执行了,而且是先执行了+

6.5运算符重载在项目开发中的应用

实现一个数组类
实现一个字符串类
智能指针类编写 (没有对应详细视频)
智能指针思想:
工程中的智能指针是一个类模板
通过构造函数接管申请的内存
通过析构函数确保堆内存被及时释放
通过重载指针运算符* 和 -> 来模拟指针的行为
通过重载比较运算符 == 和 != 来模拟指针的比较

#创智播客C++学习#

猜你喜欢

转载自blog.csdn.net/FAKER_0X0/article/details/89406387
今日推荐