文章目录
类与对象有三个重要的特性:封装,继承,多态
基本概念
- 具有相同性质的对象,抽象为类
- 类中的属性和行为,统一称为成员
- 属性(成员属性)(成员变量)
- 行为(成员函数)(成员方法)
封装中的权限:
作用域
- public 公共权限 【类内可访问,类外可访问】
- protected 保护权限 【类内可访问,类外不可访问】【子类可以访问父类中的保护内容】
- private 私有权限 【类内可访问,类外不可访问】【子类不可以访问】
在类中,一般将成员属性设为私有,用函数提供对外的接口。
优点:
- 可以自己控制读写的权限
- 对于写权限,我们可以检测数据的有效性(比如年龄不可以输入-10,只能输入我们规定的范围)
在类中可以让另一个类作为本来类的成员
构造函数
用于对象的初始化
语法
- 类名(){} 对成员进行初始化操作
- 不用写void,没有返回值
- 函数名称与类相同
- 可以有参数,因此可以发生重载
- 调用对象时,自动调用构造,无需手动调用,而且只会调用一次
构造函数的分类与调用
两种分类方式
按参数分:有参构造和无参构造(默认构造)
#include <iostream>
#include <string>
using namespace std;
//创建一个person类
class person
{
public:
//有参构造函数
person(int age, string name)
{
m_age = age;
m_name = name;
}
//无参构造
//重载
person()
{
m_age = 10;
m_name = "小芳";
}
int m_age;
string m_name;
};
int main()
{
//创建一个对象
//调用有参构造
person p1(18, "小滨");
//调用无参构造
person p2;
cout << "P1:" << endl;
cout << "姓名:" << p1.m_name << " 年龄:" << p1.m_age << endl;
cout << "P2:" << endl;
cout << "姓名:" << p2.m_name << " 年龄:" << p2.m_age << endl;
system("pause");
return 0;
}
按类型分:拷贝构造函数(person(const person&p)),普通构造函数
#include <iostream>
#include <string>
using namespace std;
//创建一个person类
class person
{
public:
//拷贝构造函数
person(const person&p)
{
m_age = p.m_age;
m_name = p.m_name;
}
//普通构造函数
//重载
person()
{
m_age = 21;
m_name = "小滨";
}
int m_age;
string m_name;
};
int main()
{
//创建一个对象
//调用普通构造
person p2;
//调用拷贝构造
person p1(p2);
cout << "P1:" << endl;
cout << "姓名:" << p1.m_name << " 年龄:" << p1.m_age << endl;
cout << "P2:" << endl;
cout << "姓名:" << p2.m_name << " 年龄:" << p2.m_age << endl;
system("pause");
return 0;
}
注意事项:
- 匿名对象 person(10);当前行 执行完毕后,系统会立即回收掉匿名对象
- 不要利用拷贝函数,初始化匿名对象
构造函数的调用规则
- 只要创造一个类,C++编译器会给每个类添加至少三个函数
- 无参构造
- 有参构造
- 拷贝构造(值拷贝)
如果用户提供有参,则编译器不提供无参,但是还是会有拷贝构造
如果用户写了一个拷贝构造,则编译器不提供有参和无参
类中构造函数初始化列表
直接看代码
#include <iostream>
using namespace std;
//创建一个person类
class person
{
public:
//构造
person(int age):m_age(age)
{
cout << "调用有参构造" << endl;
}
~person()
{
cout << "调用析构函数" << endl;
}
int m_age;
};
void test()
{
person p(18);
cout << "年龄:" << p.m_age << endl;
}
int main()
{
test();
system("pause");
return 0;
}
析构函数
用于对象的清理
语法
- ~类名(){} 进行清理操作
- 不用写void,没有返回值
- 函数名称为在类名称前加~
- 不可以有参数,无法重载
- 程序在对象销毁前会自动调用析构,无需手动调用,而且只会调用一次
#include <iostream>
using namespace std;
//创建一个person类
class person
{
public:
//构造
person()
{
cout << "调用无参构造" << endl;
}
~person()
{
cout << "调用析构函数" << endl;
}
};
void test()
{
person p;
}
int main()
{
test();
system("pause");
return 0;
}
深拷贝与浅拷贝
背景:
在堆区开辟空间,析构函数进行释放时会重复释放堆区内存。
#include <iostream>
#include <string>
using namespace std;
//创建一个person类
class person
{
public:
//有参构造
person(int age,int height)
{
m_age = age;
//将数据创建在堆区
m_height = new int(height);
cout << "调用有参构造" << endl;
}
person(const person& p)
{
m_age = p.m_age;
//编译器自己写的浅拷贝,导致队去内存重复释放
//m_height = p.m_height;
//自己写深拷贝
m_height = new int(*p.m_height);
}
~person()//释放堆区内存
{
if (m_height != nullptr)
{
delete m_height;
m_height = nullptr;
}
}
int m_age;
int *m_height;
};
int main()
{
person p1(10,170);
cout << "年龄" << p1.m_age << " 身高:" << *p1.m_height << endl;
//编译器浅拷贝
person p2(p1);
cout << "年龄" << p2.m_age << " 身高:" << *p2.m_height << endl;
system("pause");
return 0;
}
浅拷贝:简单的赋值拷贝操作 => 堆区的内存重复释放。
person(const person& p)
{
m_age = p.m_age;
//编译器自己写的浅拷贝,导致队去内存重复释放
m_height = p.m_height;
}
深拷贝:在堆区重新申请空间,进行拷贝构造 =>可解决堆区内存重复释放。
person(const person& p)
{
m_age = p.m_age;
//编译器自己写的浅拷贝,导致队去内存重复释放
//m_height = p.m_height;
//自己写深拷贝,在堆区开辟一块内存,进行拷贝构造
m_height = new int(*p.m_height);
}
静态成员变量
- 所有对象共享一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
`
静态成员变量不属于某一对象上,所有对象都共享这份数据
两种访问方式
按照类名访问
- 如果该成员变量为公有的可以访问
- 如果为保护或私有的不可以访问
按照对象访问
- 如果该成员变量为公有的可以访问
- 如果为保护或私有的不可以访问
#include <iostream>
using namespace std;
//创建一个person类
class person
{
public:
person()
{
cout << "调用构造函数" << endl;
}
~person()
{
cout << "调用析构函数" << endl;
}
static int m_age;
};
//类内声明,内外初始化
int person::m_age = 18;
void test()
{
//所有成员共用一份数据 m_age=18
//两种方式
//根据类名访问
cout << "年龄:" << person::m_age << endl;
//根据对象访问
person p1;
cout << "p1的年龄:" << p1.m_age << endl;
}
int main()
{
test();
system("pause");
return 0;
}
静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量(在类内声明,在类内实现)
两种访问方式
按照类名访问
- 如果该成员函数为公有的可以访问
- 如果为保护或私有的不可以访问
按照对象访问
- 如果该成员函数为公有的可以访问
- 如果为保护或私有的不可以访问
#include <iostream>
using namespace std;
//创建一个person类
class person
{
public:
person()
{
cout << "调用构造函数" << endl;
}
~person()
{
cout << "调用析构函数" << endl;
}
static void set_age(int age)
{
m_age = age;
}
static int m_age;
};
//类内声明,内外初始化
//必须要有初始化
int person::m_age = 18;
void test()
{
//所有成员共享同一个函数
//两种方式
//根据类名访问
person::set_age(20);
cout << "p2的年龄:" <<person::m_age << endl;
//根据对象访问
person p1;
p1.set_age(21);
cout << "p1的年龄:" << p1.m_age << endl;
}
int main()
{
test();
system("pause");
return 0;
}
类和对象的存储空间
空对象占用空间为1个字节,因为每个空对象也该有一个独一无二的地址
#include <iostream>
using namespace std;
//创建一个person类
class person
{
};
void test()
{
cout <<"空对象为:"<< sizeof(person)<<"个字节" << endl;
}
int main()
{
test();
system("pause");
return 0;
}
成员变量和成员函数分开存储
只有一个成员变量时,占四个字节
//创建一个person类
class person
{
public:
//void set(int A)
//{
// m_A = A;
//}
int m_A;
};
void test()
{
cout << "该类占" << sizeof(person) << "个字节" << endl;
}
加一个成员函数时,还是占四个字节
成员函数不属于对象上
//创建一个person类
class person
{
public:
void set(int A)
{
m_A = A;
}
int m_A;
};
void test()
{
cout << "该类占" << sizeof(person) << "个字节" << endl;
}
this指针
编译器内置指针,指向被调用的成员函数所属对象
this指针的本质是指针常量
常用场景(用于初始化)
#include <iostream>
using namespace std;
//创建一个person类
class person
{
public:
person(int a)
{
this->m_A = a;
}
int m_A;
};
void test()
{
person p(10);
cout << "年龄:" << p.m_A << endl;
}
int main()
{
test();
system("pause");
return 0;
}
返回对象本身用 * this;
空指针调用成员函数
注意this指针的存在
会存在this指向空指针的操作,加上以下代码可解决
void setAge(int age)
{
if (this == nullptr)
{
return;
}
m_age = age;//这里默认写的是this->m_age=age;
}
全部测试代码如下:
#include <iostream>
using namespace std;
//创建一个person类
class person
{
public:
void showmess()
{
cout << "这是一个person类" << endl;
}
void setAge(int age)
{
if (this == nullptr)
{
return;
}
m_age = age;//这里默认写的是this->m_age=age;
}
int m_age;
};
void test()
{
person *p=nullptr;
p->showmess();
p->setAge(18);//this 指向空指针
}
int main()
{
test();
system("pause");
return 0;
}
常函数
const修饰成员函数
在成员函数的后面加一个const关键字,修饰的是this指向让指针指向的值也不可以改变。
1.改变this指向,报错
void setAge(int age)const
{
this->m_age = age;
}
int m_age;
2.在定义变量前加一个mutable关键字,即可改变该变量的值。
#include <iostream>
using namespace std;
//创建一个person类
class person
{
public:
void setAge(int age)const
{
this->m_age = age;
}
//int m_age;
mutable int m_age;
};
void test()
{
person p;
p.setAge(18);
cout << p.m_age << endl;
}
int main()
{
test();
system("pause");
return 0;
}
常对象
常对象只可以调用常函数
//关键代码
void setAge(int age)const
const person p1;
p1.setAge(18);
友元函数
关键字 friend
作用:私有属性让某些外部人员访问
全局函数做友元
#include <iostream>
using namespace std;
//创建一个person类
class person
{
//写在类里面的最上面,前面加一个关键字friend
friend void viewFriend(person* p);
public:
person()
{
this->m_height = 170;
this->m_age = 18;
}
int m_age;
private :
int m_height;
};
void viewFriend(person *p)
{
cout <<"年龄:" << p->m_age << endl;
//可以调用私有成员 m_height
cout << "身高:" << p->m_height << endl;
}
void test()
{
person p;
viewFriend(&p);
}
int main()
{
test();
system("pause");
return 0;
}
类做友元
#include <iostream>
using namespace std;
//创建一个person类
class person
{
//写在类里面,前面加一个关键字friend
friend class fri;
public:
person()
{
this->m_height = 170;
this->m_age = 18;
}
int m_age;
private :
int m_height;
};
class fri
{
public:
fri()
{
//创建一个person类
p = new person;
}
void visit()
{
cout << p->m_age << endl;
cout << p->m_height << endl;
}
~fri()
{
delete p;//析构函数清除堆区内存
}
person* p;
};
void test()
{
fri f;
//访问person的私有成员
f.visit();
}
int main()
{
test();
system("pause");
return 0;
}
成员函数做友元
#include <iostream>
using namespace std;
class building;
class goodgay
{
public:
goodgay();
//释放内存
~goodgay()
{
if (b != nullptr)
{
delete b;
b = nullptr;
}
}
void vis();
building* b;
};
class building
{
//友元成员函数
friend void goodgay::vis();
public:
building();
public:
string m_sit;
private:
string m_bed;
int m_age;
};
/*
*类内函数的类外实现
*/
building::building()
{
m_sit = "客厅";
m_bed = "卧室";
m_age = 18;
}
goodgay::goodgay()
{
b = new building;
}
void goodgay::vis()
{
cout << b->m_sit << endl;
cout << b->m_bed << endl;
cout << b->m_age << endl;
}
//测试函数
void test()
{
goodgay g;
g.vis();
}
int main()
{
test();
system("pause");
return 0;
}