4 类和对象-三大特性(封装、继承、多态)
4.1封装
4.1.1封装的意义
- 将 属性和行为作为一个整体,表现生活中的事物
- 对属性和行为进行权限约束
//举例-圆类
class Circle
{
? public:
? ? ? ? ? ? ? //属性-成员变量?
? ? ? ? ? ? ? int m_r;
? ? ? ? ? ? ? ?//行为-函数
? ? ? ? ? ? ? ?double calculateZC()
? ? ? ? ? ? ? ?{?
? ? ? ? ? ? ? ? ? ?return 2 * PI * m_r;
? ? ? ? ? ? ? ?}
}
int main()
{
? ?Circle c1;
? ?c1.m_r = 10;
? ?cout << "圆的周长" << c1.calculateZC() << endl;
? ?return 0;
}
4.1.2封装的访问权限
三种:?
public | 公共权限 | 成员 | ?类内可以访问、类外也可以访问 | |
protected | 保护权限 | 成员? | 类内可以访问、类外不可以访问? | ?继承中,儿子可以访问父亲的保护内容 |
private | 私有权限? | 成员 | 类内可以访问、类外不可以访问 | ?继承中,儿子不可以访问父亲私有的内容 |
4.1.3struct和class区别
- struct默认权限为public
- class默认权限为private
class C1
{
int m_A;//默认为private
}
struct C2
{
int m_B;//默认为public
}
4.1.4将成员属性设置为私有
优点:将所有成员属性设置为私有,可以自己控制读写权限(也就是在类内根据权限控制要求,提供相应的方法去获取或修改私有属性,就是java的get、set方法)
class person
{
private:
int m_Age;
String m_name;
String m_Lover;
public:
void setAge(int m_Age)
{
m_Age = m_Age;
}
int getAge()
{
return m_Age;
}
void setLover(String Lover)
{
m_Lover = Lover;
}
int getName()
{
return m_Name;
}
}
优点:可以检测数据的有效性
class person
{
int m_Age;
public:
void getAge(int age)
{
if(age < 0 || age > 150)
{
cout << "error" << endl;
return;
}
m_Age = age;
}
}
?4.2对象的初始化和清理
4.2.1构造函数和析构函数
构造函数:主要用于在创建对象时为对象的成员属性赋值,该函数无需手动调用,由编译器自动调用。
- 没有返回值、不用写void
- 函数名和类名相同
- 构造函数可以有参数发生重载
- 无需手动调用
析构函数:主要用于在对象销毁前系统自动调用,执行一些清理操作。
- 没有返回值、不用写void
- 函数名和类名相似前面多了~
- 没有参数不发生重载
- 只发生一次
4.2.2构造函数的分离和调用
两种分类方式:
按参数划分:有参构造和无参构造
按类型划分:普通构造和拷贝构造
Example.h代码
class Example
{
public:
Example();
Example(const Example& example);
~Example();
public:
int a;
};
Example.cpp代码
Example::Example()
{
a = 10;
cout << "普通构造-无参构造" << endl;
cout << "a = " << a << endl;
}
Example::~Example()
{
}
//拷贝构造
Example::Example(const Example &example)
{
a = example.a;
cout << "拷贝构造" << endl;
}
main代码
int main()
{
Example e = Example();
Example f = Example(e);//拷贝构造
cout << f.a << endl;
}
运行截图:
?
三种调用方式:
- 括号法-就是构造函数的重载调用罢了??
- 显示法-就是Example exp?= Example(example);
- 匿名对象-Example(10);//特点,当前行执行完后直接被销毁无法在后面继续使用
- 隐式转换法-Example exp = 10;//相当于Example exp = Example(10);
注意:不要用拷贝构造初始化匿名对象 编译器认为是对象的声明
4.2.3拷贝构造函数的调用时机
- 使用一个已经创建完毕的对象来初始化一个新对象
//就是拷贝构造
class Person
{
? ? ?public:
? ? ? ? ?int age;
}?
void test01()
{
? ? ? ?Person p1 = Person(20);
? ? ? ?Person p2 = Person(p1);
}
- 以值传递的方式给函数参数传值
//就是对象作为函数形参时,在被调函数中会通过拷贝构造去拷贝一个新的对象在被调函数
//中使用
void func(Person p)
{
? ? ?
}
void test02()
{
? ? ? ?Person p1 = Person(20);
? ? ? ?func(p1);
}
- 以返回值的方式返回局部变量
//就是通过拷贝构造去返回对象
Person func()
{
? ? ? ?Person p1 = Person(20);
? ? ? ?return p1;
}
void test03()
{
? ? ? ?Person p1 = func();
}
4.2.4构造函数的调用规则
-
默认情况下,编译器会提供三个构造函数,即无参构造、有参构造、拷贝构造
-
若用户主动提供有参构造,则编译器不会提供无参构造,但提供拷贝构造
-
若用户主动提供拷贝构造,则编译器不会提供无参构造和有参构造?
4.2.5深拷贝与浅拷贝 *
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
案例:对于一个类,其中有int m_a;和 int *m_b;两个成员变量,对于m_b而言是通过有参构造即m_b = new int(b);其中b为传入值来赋值的,因此在堆区,需要通过析构函数进行手动释放,若直接采用编译器提供的拷贝构造函数来进行调用,则先释放拷贝函数构造的对象时,m_b的空间即被释放了,会导致原来的对象无m_b指向的空间去释放。因此产生下图错误。
//案例代码
//Example.h code
class Example
{
public:
Example();
Example(int a,int b);
//Example(const Example& example);
~Example();
public:
int m_a;
int* m_b;
};
//Example.cpp code
#include "Example.h"
Example::Example(int a,int b)
{
m_a = a;
m_b = new int(b);
cout << "普通构造-有参构造" << endl;
cout << "a = " << m_a << endl;
cout << "a = " << *m_b << endl;
}
Example::~Example()
{
if (m_b != NULL)
{
delete(m_b);
m_b = NULL;
}
}
//main函数
void test()
{
Example e = Example(4,5);
Example f = Example(e);//拷贝构造
}
int main()
{
test();
}
解决方法:深拷贝即重写拷贝构造将编译器提供的构造语句
m_b = example.m_b;
修改为重新获得一个内存区域存放值,并将m_b指向该区域。
Example::Example(const Example& example)
{
?? ?m_a = example.m_a;
?? ?m_b = new int(*example.m_b);//修改的语句,实现深拷贝
?? ?cout << "拷贝构造" << endl;
}
4.2.6初始化列表-属性赋值
语法:构造函数()属性1(值1),属性2(值2)...{}
//传统有参构造赋初值
Example(int a,int b,int c)
{
m_a = a;
m_b = b;
m_c = c;
}
//初始化方法为属性赋初值
Example(int a, int b ,int c):m_a(a),m_b(b),m_c(c)
{
}
注:
1.对于初始化列表要注意的是其效果相当于在构造函数内进行赋值,但不完全等同,若
const 修饰的变量其不能通过赋值来初始化,因此只能通过初始化列表的方式。
若普通的变量,初始化列表和构造函数内赋值效果相同。?
class myClass { public : myClass();// 构造函数,无返回类型,可以有参数列表,这里省去 ~myClass();// 析构函数 int a; const int b; } myClass::myClass():a(1),b(1)// 初始化列表 { }
并且对于
引用变量(引用是一种别名,它允许我们使用一个已存在的对象来创建一个新的名称。引用变量在声明时必须进行初始化,并且一旦初始化后,它将一直引用同一个对象,无法改变引用的目标。)也需要创建时就进行初始化
class Example
{
public:
Example(int &_ref);
~Example();
public:
const int m_c;
int & ref;
}
Example::Example(int &_ref):m_c(1),ref(_ref)
{
}
//mian函数
void main()
{
int c = 10;
Example e = Example(c);
c = 20;
cout << e.m_c << "ref" << e.ref << "c:" << c << endl;
}
运行截图:
?
?
2.初始化时要和参数的顺序一致,不然出现赋值错乱。
4.2.7类对象作为类成员
主要关注谁先调用和销毁
当其他类作为本类的成员变量时,
构造时,先构造其他对象再构造自身
析构时,先析构自身再析构其他对象
4.2.8静态成员
静态成员就是在成员变量和成员函数前加上关键字static,包括静态成员属性和静态成员函数
- 静态成员属性
1.所有对象共享同一份数据
2.在编译阶段分配内存
3.类内声明,类外初始化
- 静态成员函数
1.所有对象共享同一个函数
2.静态成员函数只能访问静态成员变量
静态成员代码示例:
//Example.h
class Example
{
public:
static int m_A;//静态成员变量-公有
private:
static int m_B;//静态成员变量-私有
};
//Example.cpp
无
//注意一定在类外给静态成员变量进行初始化,不能在另一个类中,在main之前可以
//mian
int Example::m_A = 10s;
int main()
{
cout << Example::m_A << endl;
Example e;
e.m_A = 20;
cout << e.m_A << endl;
cout << Example::m_A << endl;
system("pause");
return 0;
}
运行结果:
静态成员函数代码示例:
//Example.h
class Example
{
public:
static void func();
public:
static int m_A;//静态成员变量-公有
private:
static int m_B;//静态成员变量-私有
};
//Example.cpp
void Example::func()
{
cout << "static function 被调用了" << endl;
m_A = 100;//只能访问静态成员变量
cout << "static function 调用了静态成员变量并修改为100,m_A=" << m_A << endl;
}
//mian
int main()
{
cout << Example::m_A << endl;
Example e;
e.m_A = 20;
cout << e.m_A << endl;
cout << Example::m_A << endl;
Example::func();
e.func();
system("pause");
return 0;
}
运行截图:
为什么静态函数只能访问静态变量?因为静态成员变量和静态成员函数是在对象创建之前就已经分配了空间所确定的,假设静态成员函数访问非静态成员,则有可能非静态成员还没有被创建,肯定就无法访问咯。还有一种解释,因为所以对象共用静态成员函数,可能调用非静态成员变量时并不知道访问哪一个对象的非静态变量的值,这不就矛盾了那也不能访问咯。
4.3C++对象模型和this指针
4.3.1成员变量和成员函数分开存储
若创建一个空对象,对象内既无成员变量也无成员函数则其所占内存为sizeof 为 1
非静态成员变量属于该类的对象上
静态成员变量不属于该类的对象上
非静态成员函数不属于类对象上
静态成员函数不属于该类对象上?
4.3.2 this指针
this指针指向的是被调用的成员函数所属的对象? this-> 注意是:->
return *this;? 表示返回的是当前被调用的对象
//一、为了防止重名
类中有成员变量age
Example::Example(int age)
{
this->age = age;
}
//*this返回对象
//Example.h
class Example
{
Example(int age);
Example& add_age_ref(Example p);
Example add_age_unref(Example p);
int age;
};
//Example.cpp
Example& Example::add_age_ref(Example p)
{
// TODO: 在此处插入 return 语句
this->age += p.age;
return *this;
}
Example Example::add_age_unref(Example p)
{
this->age += p.age;
return *this;
}
//mian
int main()
{
Example e = Example(18);
cout << "this->age:" << e.age << endl;
Example *e1 = new Example();
e1->add_age_unref(e).add_age_unref(e).add_age_unref(e);
cout << "返回为Exaple &类型的age调用add_age_unref()函数二次后" << e1->age << endl;
e1->add_age_ref(e).add_age_ref(e).add_age_ref(e);
cout << "返回为Exaple &类型的age调用add_age_ref()函数二次后" << e1->age << endl;
system("pause");
return 0;
}
运行结果:
?辨析 *:
new对象和构造对象的区别:
new的对象是在堆区,因此需要手动管理和释放,而构造产生的对象是在栈,有析构函数进行删除,根据作用域范围,超出自动删除。
->和.的区别:
- A.B则A为对象或者结构体; 点号(.):左边必须为实体。
- A->B则A为指针,->是成员提取,A->B是提取A中的成员B,A只能是指向类、结构、联合的指针; 箭头(->):左边必须为指针;
指针用->,对象用.
返回对象Example? 与? ?Example & 的区别:
对于Example而言返回的时候是重新copy了一个新的进行返回,其实编译器支持 C++11 及以上的标准,并且对象具有可移动语义(例如,具有移动构造函数和移动赋值运算符),那么返回一个对象时,编译器可能会使用移动语义而不是拷贝构造函数,从而减少对象的复制。因此此时
e1->add_age_unref(e).add_age_unref(e).add_age_unref(e);始终是新的对象,则并不会叠加age的值,即是初始值18.
而对于Example &而言返回的是同一个对象,因此此时
e1->add_age_ref(e).add_age_ref(e).add_age_ref(e);是同一个对象,age变会累加四次,即是72.
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!