类与对象的概念:
类:是一个概念,对同一类对象的抽象总结。
对象:按照类的规定创建的实体。对象无法脱离对应的类存在。
一个类主要包括:
属性 描述对象的数据元素,也称 成员变量
行为 描述对象的执行的具体操作,也称成员函数
类的定义:
定义:class 类名{ };
public:公有权限
实例化对象:
栈内存对象:Phone mp1;
生命周期结束后,自动被销毁。
对象使用. 调用成员。
堆内存对象:Phone *mp2=new Phone;
new创建,delete销毁,不销毁会持续存在,容易导致内存泄漏
使用指针来保存地址,使用->调用成员
封装
黑盒:封装,隐藏一些类的属性和细节,重新公开外部调用的接口。
白盒:所有类的细节都公开。
封装的写法不唯一。通常先对属性私有化,使属性隐藏,然后根据需求通过
getter(读属性)和setter(写属性)对类分别公开读和写的功能。
string get_brand()
{
return brand;
}
void set_brand(string b)
{
brand = b;
}
构造函数
·如果一个类中,程序员不手动编写构造函数,编译器会为这个类自动添加一个无参构造函数,且此函数的函数体为空;
·构造函数类内一种特殊的函数,用来创建一个对象。
·构造函数要求函数名必须与类名完全一致,且构造函数无需写返回值。
1 编译器自动添加的无参构造函数
MobilePhone(){}
2 手写构造函数 ,可以设定属性的默认值
MobilePhone()
{
model = "8848钛金手机";
weight = 188;
cout << "生产了一部手机" << endl;
}
main里面
MobilePhone mp1; // 调用了无参构造函数
mp1.set_brand("小米");
.....
3 传参。可以给构造函数增加参数,使用参数给属性赋予初始值
MobilePhone(string b,string m,int w)
{
brand = b;
model = m;
weight = w;
}
main函数里
MobilePhone mp1("小米","13Pro",190); // 调用了三参构造函数
4 重载。构造函数也支持函数重载,遵守之前函数重载的规则
即同一个函数名可以用多次
例子就是参数和手写的,函数名相同,没啥好看的
5 参数默认值。构造函数也支持之前的参数默认值设定。
MobilePhone(string b = "8848",string m="钛金手机")
{
brand = b;
model = m;
}
6 构造初始化表。是一种简便的写法,可以用于给属性赋予初始值。
MobilePhone(string b,string m,int w):brand(b),model(m),weight(w){}
从1-6是一直简化的过程。
拷贝构造函数
·如果程序员在一个类中不手动编写拷贝构造函数,编译器会为这个类自动添加一个拷贝构造函数
·拷贝构造函数与普通构造函数也是函数重载的关系。
·拷贝构造函数可以把一个对象的属性值拷贝到新创建的对象中。
因为编译器会自动添加,所以不写了吧,笔记上有,就是挨个赋值
函数名称:// MobilePhone(const MobilePhone& mp)
主函数:MobilePhone mp2(mp1);
拷贝构造函数可能会出现什么问题?
如果成员变量出现指针,在拷贝的过程中,会导致两个对象的成员变量指针保存同一份地址,指向同一个内存区域,不符合面向对象的特性。(虽然没有错误
·深拷贝和浅拷贝
当类中的成员变量出现指针,默认的浅拷贝。需要手写构造函数,为每 个对象的name属性单独开辟一个内存区域,且当前对象独享这个区域。
应该是没整通透
Dog(char* n)
{
name = new char[20];
strcpy(name,n);// 只拷贝内容
}
Dog(const Dog& d)
{
name = new char[20];
strcpy(name,d.name); 只拷贝内容
}
main里面
Dog d1(c); // d1.name → 堆区1
Dog d2(d1); // d2.name → 堆区2
其实在实际开发中,也可以采用屏蔽拷贝构造函数这种简单粗暴的方式解决浅拷贝问题。屏蔽某个构造函数只需要把这个构造函数的代码从public区域移动到private区域。
析构函数
构造函数 | 析构函数 |
手动调用 | 在对象被销毁时自动调用 |
通常用于在对象创建时初始化 | 通常用于在对象销毁时回收资源 |
可以被重载 | 不可以重载,因为没有参数 |
函数名是类名 | 函数名是~类名 |
~Cat(){}
深拷贝代码中,两个构造函数中都new出了name属性对应的堆内存空间,但是并没有回收,因此需要给Dog类增加析构函数,在析构函数中回收name占用的堆内存空间。
// 析构函数
~Dog()
{
delete name;
}
作用域限定符::
·std是C++标准库的一个名字空间,很多使用的内容都是来自于标准名字空间,例如字符串std::string、std::cout...
·当项目中包含using namespace std;时,代码中使用std名字空间中的内容就可以省略前面的std::
cout << my_space::a << endl;
cout << b << endl; // 实际上是my_space::b
类内声明,类外定义
·对于类中的成员也可以声明定义分离,如果声明定义分离,通常在类内声明,在类外定义,类外的定义需要结合作用域限定符使用。
string get_str(); // getter// 类内声明
// 类外定义
string Test::get_str() //类名叫Test
{
return str; // 也属于类内
}
explicit关键字
如果赋值时,刚好赋值运算符右边的数值是左边类的构造函数可以接收的类型,编译器则会自动调用这个构造函数并把赋值运算符右边的数值传入构造函数中,相当于隐式调用了构造函数。
有时候在一些参数传递的过程中,隐式构造可能会在程序员无意的情况下触发了构造函数,因此可以在构造函数前添加explicit关键字修饰,屏蔽隐式调用的用法。
this指针
·this指针是一个特殊的指针,保存的是当前类的对象首地址。
·判断指向:this所在的函数是哪个对象的,this指向的就是这个对象。
Test t1;
cout << &t1 << endl; // 0x61fe8b
t1.test_this(); // 0x61fe8b
函数里只是个打印this的
原理:
在类内调用此类的成员,虽然不用手写this指针,但是编译器都会使用this指针来调用成员,因为成员只能由对象来调用,而this指针指向的就是当前类的对象。
Student(string s)
{
this->name = s;
}
string get_name()
{
return this->name;
}
void show()
{
cout << this->get_name() << endl;
}
应用:
·区分重名的成员变量与局部变量
·链式调用
·多态传参
static关键字
static关键字在类内有以下几种用法:
·静态局部变量
·静态成员变量
·静态成员函数
静态局部变量
使用static关键字修饰局部变量就是静态局部变量。
所有的对象都共享一个静态变量
代码待添加
静态局部变量所在的函数第一次被调用时,静态局部变量创建,在程序结束时才销毁
静态成员变量
·静态成员变量可以直接使用类名::来调用,更推荐使用此方式
·此类的所有对象共用此变量
·非const的静态成员变量通常需要类内声明,类外初始化
·静态成员变量在程序运行时创建,在程序结束时销毁