类和对象基础(C++)
目录
1.struct和class的异同
1.1 C语言中的struct
1.2 C++中的struct
1.3 class
2.类的定义
3.访问限定符
3.1访问限定符的作用域
3.2成员属性设置为私有
4.类的默认成员函数
4.1构造函数和析构函数
4.2拷贝构造函数
5.深拷贝和浅拷贝
6.再谈构造函数
6.1初始化列表
6.2类对象作为类成员
6.3静态成员
7.C++对象模型和this指针
7.1成员变量和成员函数分开存储
7.2this指针的概念
7.3空指针访问成员函数
7.4const修饰成员函数
1.struct和class的异同
1.1 C语言中的struct
C语言中,struct可以定义结构体:
struct SNode
{
struct SNode* next;
int val;
};
结构体中只能有变量,而不能出现函数。
1.2 C++中的struct
在C++中,兼容了C语言中使用struct定义结构体的语法,而且新增加了一种用法:使用struct定义类。
struct在C++和C语言中的不同点:
- C语言定义的结构体中只能出现变量; C++定义的类中可以出现函数。
1.3 class
C++中可以使用struct定义类,也可以使用class来定义类,这两种定义方式也有区别:
- struct定义的默认成员时公有的; class定义的默认成员时私有的。
提到公有、私有,就不得不谈一谈访问限定符了。
2.类的定义
语法:class 类名{ 访问权限: 属性/行为};
实例代码:
//设计一个圆类
const double PI = 3.14;
class Circle
{
public: //访问权限 公共的权限
//属性
int m_r;//半径
//行为
//获取到圆的周长
double calculateZC()
{
//2 * pi * r
//获取圆的周长
return 2 * PI * m_r;
}
};
3.访问限定符
三种访问限定符:
- public(公有的) 在类内和类外都可以直接访问
- protected(保护的)
- private(私有的) protected和private都是在类内可以访问,在类外不能访问(具体的区别在继承,在这里不进行区分)
提到类和对象,就少不了访问限定符,类对成员的封装,就借助了访问限定符。
上面我们提到struct定义的类默认是共有的,class定义的类默认是私有的,下面我们写一段程序,来进一步理解:
struct person
{
const char* name;
int age;
void show()
{
cout << name << " " << age << endl;
}
};
int main()
{
person p1;
p1.name = "zhangsan";
p1.age = 18;
p1.show();
return 0;
}
默认权限是公有的,在类外和类内都可以直接访问。
把以上代码中的struct 改为class,程序就会报错:
原因就是class定义的类成员默认是私有属性,在类外不能直接访问。
但如果我们在类中使用访问限定符之后,就能解决这个问题:
class person
{
public:
void show()
{
cout << name << " " << age << endl;
}
const char* name;
int age;
};
int main()
{
person p1;
p1.name = "zhangsan";
p1.age = 18;
p1.show();
return 0;
}
3.1访问限定符的作用域
1.从一个访问限定符到另一个访问限定符。2.从一个访问限定符到类结束。
class person
{
public: public和private之间的是public属性
void show()
{
cout << name << " " << age << endl;
}
private: private到类定义结束时private属性
const char* name;
int age;
};
3.2成员属性设置为私有
优点:
- 将所有成员属性设置为私有,可以控制读写权限
- 对于写权限,可以检测数据的有效性
class Person {
public:
//姓名设置可读可写
void setName(string name) {
m_Name = name;
}
string getName()
{
return m_Name;
}
//获取年龄
int getAge() {
return m_Age;
}
//设置年龄
void setAge(int age) {
if (age < 0 || age > 150) {
cout << "你个老妖精!" << endl;
return;
}
m_Age = age;
}
//情人设置为只写
void setLover(string lover) {
m_Lover = lover;
}
private:
string m_Name; //可读可写 姓名
int m_Age; //只读 年龄
string m_Lover; //只写 情人
};
4.类的默认成员函数
4.1构造函数和析构函数
对象的初始化和清理是两个非常重要的安全问题,一个对象或者变量没有初始化状态的话,那么造成的后果是严重的。
同样的使用完一个对象或者变量,没有及时清理的话,也会造成安全问题。
C++利用构造函数和析构函数来解决以上问题,这两个函数由编译器自动调用,完成对象的初始化和清理工作;
我们不需要提供构造和析构函数,编译器会提供构造和析构函数的空实现。
构造函数语法:类名(){}
- 构造函数,没有返回值也不写void
- 函数名和类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象的时候自动进行构造,无需手动调用,而且只调用一次
构造函数的调用规则:
默认情况下,C++编译器至少给一个类添加3个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无惨,函数体为空)
- 默认拷贝函数,对属性进行拷贝
构造函数的调用规则如下:
- 如果用户定义有参构造函数,C++不会提供默认构造函数,但是会提供拷贝函数
- 如果用户定义拷贝构造函数,C++不会再提供其他构造函数
//以person类为例
class person
{
public:
void show()
{
cout << name << " " << age << endl;
}
person() //构造函数
{
cout << "person()" << endl;
}
private:
const char* name;
int age;
};
析构函数的语法:~类名(){}
- 析构函数也是没有返回值的,也不写void
- 函数名和类名相同,在名称的前面加上~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前自动调用析构函数,无需手动调用,而且只调用一次
class person
{
public:
//析构函数
~person()
{
cout << "~person()" << endl;
}
};
4.2拷贝构造函数
class person
{
public:
person()
{
cout << "person()" << endl;
}
person(const char* name, int age)
{
_name = name;
_age = age;
}
private:
const char* _name;
int _age;
};
int main()
{
person p1("lisi", 20);//调用构造函数
person p2(p1);//调用拷贝构造
return 0;
}
通过调试可以看到,p2初始化的值就是拷贝的p1对象的,而且拷贝构造函数是编译器自动生成的。
5.深拷贝和浅拷贝
- 浅拷贝:简单的赋值拷贝操作
- 深拷贝:在堆区重新申请空间,进行拷贝操作
class Person {
public:
//无参(默认)构造函数
Person() {
cout << "无参构造函数!" << endl;
}
//有参构造函数
Person(int age ,int height) {
cout << "有参构造函数!" << endl;
m_age = age;
m_height = new int(height);
}
//拷贝构造函数
Person(const Person& p) {
cout << "拷贝构造函数!" << endl;
//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
m_age = p.m_age;
m_height = new int(*p.m_height);
}
//析构函数
~Person() {
cout << "析构函数!" << endl;
if (m_height != NULL)
{
delete m_height;
}
}
public:
int m_age;
int* m_height;
};
浅拷贝可能导致的问题和解决办法:
进行拷贝时,这两个对象指向的是同一块空间,而在使用析构函数对每个对象进行销毁时都会释放这块空间,这就会造成堆区空间重复释放的问题。 解决办法:深拷贝,在堆区开辟不同的空间。
6.再谈构造函数
6.1初始化列表
作用:C++提供了初始化列表的语法,用来初始化属性。
语法:构造函数(): 属性1(值1), 属性2(值2)...{}
代码举栗:
class Person {
public:
//初始化列表方式初始化
Person(int a, int b, int c)
:m_A(a)
,m_B(b)
,m_C(c)
{}
void PrintPerson() {
cout << "mA:" << m_A << endl;
cout << "mB:" << m_B << endl;
cout << "mC:" << m_C << endl;
}
private:
int m_A;
int m_B;
int m_C;
};
当然m_A,m_B,m_C也可以在构造函数内部进行初始化,这种成员变量是没有限制的;
但是类中有以下成员的话,就必须使用初始化列表才能进行初始化(不使用初始化列表是错误的):
- 引用成员变量
- const成员变量
- 自定义类型成员(且该类中没有默认构造函数)
总结:内置类型可以使用初始化列表,以上三种特殊类型只能使用初始化列表,所以能使用初始化列表的话就使用初始化列表。
6.2类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为对象成员。
class A {}
class B
{
A a;
}
创建和销毁对象的时候有一个顺序问题,构造:先调用A or 先调用B?析构:先调用A or 先调用B?
#include <iostream>
using namespace std;
class Phone
{
public:
Phone(string name)
:m_PhoneName(name)
{
cout << "Phone构造" << endl;
}
~Phone()
{
cout << "Phone析构" << endl;
}
string m_PhoneName;
};
class Person
{
public:
Person(string name, string pName)
:m_Name(name)
, m_Phone(pName)
{
cout << "Person构造" << endl;
}
~Person()
{
cout << "Person析构" << endl;
}
void playGame()
{
cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 手机" << endl;
}
string m_Name;
Phone m_Phone;
};
int main()
{
Person p("阿飞", "ipone13");
p.playGame();
return 0;
}
我们发现,初始化先调用的是Phone的构造,然后再调用的Person的构造;
销毁先调用的是Person的析构,再调用的是Phone的析构。
这个循序我们可以把Phone比作一个汽车的零件,把Person比作汽车,在组装的时候,肯定要先构造零件,再构造汽车;
在拆掉汽车的时候,要先把汽车拆掉,才能进一步拆除汽车的零件。
6.3静态成员
静态成员包括:
静态成员变量
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内生命,类外初始化
静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
类内生命,类外初始化:
class Person
{
public:
static int m_A; //静态成员变量
private:
static int m_B; //静态成员变量也是有访问权限的
};
int Person::m_A = 10;
int Person::m_B = 10;
静态成员函数:
class Person
{
public:
static void func()
{
cout << "func调用" << endl;
m_A = 100; //静态成员函数只能访问静态成员变量
}
private:
static int m_A;
int m_B;
};
7.C++对象模型和this指针
7.1成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数分开存储,只有非静态的成员变量才属于类的对象上。
class Person {
public:
Person()
{
mA = 0;
}
void func()
{
cout << "mA:" << this->mA << endl;
}
private:
int mA;
static int mB;
};
int main()
{
Person p;
cout << sizeof(p) << endl;//只有非静态的成员变量属于对象,所以输出为4
return 0;
}
7.2this指针的概念
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会公用同一块代码;
那么问题就是:这一块代码是如何区分是哪个对象调用了自己呢?答:this指针。
this指针指向被调用的成员函数所属的对象,this指针是隐含每一个非静态成员函数的一种指针。
this指针是不需要定义的,可以直接使用。
this指针的两个用途:
- 当形参和成员变量同名的时候,可以用this指针来区分(这个作用和Java中的this是相同的)
- 在类的非静态成员函数中返回对象本身,可以使用return *this
class Person
{
public:
Person(int age)
{
//1、当形参和成员变量同名时,可用this指针来区分
this->age = age;
}
Person& PersonAddPerson(Person p)
{
this->age += p.age;
return *this;
}
int age;
};
void test01()
{
Person p1(10);
cout << "p1.age = " << p1.age << endl;
Person p2(10);
p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);
cout << "p2.age = " << p2.age << endl;
}
int main()
{
test01();
return 0;
}
7.3空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是需要注意的是有没有用到this指针;
如果用到this指针,需要加以判断保证代码的健壮性。
//空指针访问成员函数
class Person {
public:
void ShowClassName() {
cout << "Person类!" << endl;
}
void ShowPerson() {
if (this == NULL) {
return;
}
cout << mAge << endl;
}
public:
int mAge;
};
void test01()
{
Person * p = NULL;
p->ShowClassName(); //空指针,可以调用成员函数
p->ShowPerson(); //但是如果成员函数中用到了this指针,就不可以了
}
int main() {
test01();
return 0;
}
成员函数在使用this指针的时候,需要进行检查。
7.4const修饰成员函数
常函数:
- 成员函数后面加const之后我们称这个函数为常函数
- 常函数内不可以修改成员属性
常对象:
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数
const对象和函数的使用:
class Person {
public:
Person()
{
m_A = 0;
m_B = 0;
}
//this指针的本质是一个指针常量,指针的指向不可修改
//如果想让指针指向的值也不可以修改,需要声明常函数
void ShowPerson() const
{
//const Type* const pointer;
//this = NULL; //不能修改指针的指向 Person* const this;
//this->mA = 100; //但是this指针指向的对象的数据是可以修改的
//const修饰成员函数,表示指针指向的内存空间的数据不能修改,除了mutable修饰的变量
this->m_B = 100;
}
void MyFunc() const
{
//mA = 10000;
}
public:
int m_A;
mutable int m_B; //可修改 可变的
};
//const修饰对象 常对象
void test01()
{
const Person person; //常量对象
cout << person.m_A << endl;
//person.mA = 100; //常对象不能修改成员变量的值,但是可以访问
person.m_B = 100; //但是常对象可以修改mutable修饰成员变量
//常对象访问成员函数
person.MyFunc(); //常对象不能调用const的函数
}
int main()
{
test01();
return 0;
}
类和对象的基础内容到这里就结束了,类和对象是我们学习面向对象语言的重点,像封装、继承、多态都和类和对象有着密不可分的关系;一定要熟练掌握!!!