目录
类的特点
类,c++程序员的工具箱中最基础且最重要的工具之一。是面向对象编程的基础。
-
什么是类?
类,是用户自定义的数据类型,类似结构体(struct)。在c语言中我们常使用struct,而在c++中我们用类比较多。
面向对象
面向对象编程(通常缩写为OOP)的本质是,根据需要解决的问题涉及的对象来编写程序。因此程序开发过程的一部分就是涉及一组类型来满足这个要求。
两大成员
属性
对象的某些特征数据,比如一个学生对象,我们可能需要定义他的姓名,学号、各科成绩等。同理,对于一个立方体,我们可能需要定义它的长、宽、高。
方法
类中提供的一些函数,比如学生对象,可以提供一个info函数,输出学生信息。sumGrade方法,返回学生总成绩的。立方体对象,我们就可以设计两个函数(方法),分别计算表面积和体积。
三大特性
封装
类的一些函数和方法,我们不想让它们在类外直接访问(通过对象直接),就可以把它们封装起来。比如学生的成绩,只允许查询而不允许修改,我们就可以把成绩封装起来,不能直接拿到学生成绩,而是通过我们定义的没被封装的函数(方法)来访问。而我们不提供修改的方法,这样就实现了只允访问,而不能修改。总结一句话,对于封装的数据,禁止直接访问该数据,但可以通过对象的成员函数来访问。
类的所有成员默认为私有,表示不能在类的外部直接访问他们。
实现:
通过c++为我们提供的public、protected、private关键字可以很轻松的实现。
继承
根据一个类定义另一个类。比如,我们先定义一个human类,属性有姓名、性别、年龄、家庭住址等。我们此时又想定义一个学生类,此时我们就可以通过继承human来继承human类的姓名、性别、年龄、家庭住址等属性,然后在另外定义成绩、学号属性就行。这样就不需要在再重新定义属性。
多态
多态表示在不同时刻有不同的形态。c++中的多态性总是设计使用指针或引用来调用对象对象的成员函数。这个说起来有些复杂,之后给代码演示。
类的定义
类是用户自定义数据类型。使用class关键字定义类。
class ClassNamed
{
// 属性
// 方法
}; // ; 不可省
定义了一个类,类的名称为ClassName。类名首字母一般为大写字母。
类的成员(方法、属性)都在类中定义(方法的实现可放在类外)。
类的成员函数默认为私有,在类外不能直接访问。可以使用public关键字后跟一个冒号,表示后面定义的成员都能在类外直接访问(直至遇到下一个private:或protected:)。
class ClassName
{
private:
// 私有成员。定义在外部不能直接访问的成员。
// something...
public:
// 公开成员。定义在外部能直接访问的成员。
// something...
};
下面我们定义一个立方体盒子
class Box
{
private:
double length; // 盒子的长
double width; // 盒子的宽
double height; // 盒子的高
public:
double get_volume() // 成员函数(方法),计算Box体积
{
double volume = length * width * height; // 计算体积
return volume; // 返回体积
}
};
类和结构体的区别
区别:C++类的成员默认是私有的,C++结构的成员默认是公开的。其他各方面,几乎一样。
在C++我们常使用类,而结构体一般是定义一些小型对象。结构体可以直接放在类中,作为类的成员属性。
类的构造函数
-
构造函数,作用是用来构造类的对象(类似用int定义变量。int a=2; a就是一个int型的对象。)。
-
本质都是一种函数,可以有参数但没有返回值。
-
没有返回值是指,我们不给出返回值,不是指返回值为空,这是一种规定。
默认调用函数和无参构造函数
class Box
{
private:
double length; // 盒子的长
double width; // 盒子的宽
double height; // 盒子的高
public:
Box() // 无参构造函数
{
length = 1.0;
width = 1.0;
height = 1.0;
cout << "调用无参构造函数" << endl;
}
};
- 默认构造函数没有参数,没有参数,没有返回值。一定是没有,而不是返回值为空。
- 如果类没有任何构造函数,编译器会自动生成一个默认的默认构造函数。
- 如果类已经定义了构造函数,此时编译器不会给出默认构造函数,此时我们需要定义无参构造函数(或者显式定义无参构造函数)。否则
Box box1;
错误。
// 测试构造函数
#include <iostream>
using namespace std;
class Box
{
private:
double length; // 盒子的长
double width; // 盒子的宽
double height; // 盒子的高
public:
// Box () = default;
Box() // 默认构造函数
{
length = 1.0;
width = 1.0;
height = 1.0;
cout << "调用无参构造函数" << endl;
}
public:
double get_volume() // 成员函数(方法),计算Box体积
{
double volume = length * width * height; // 计算体积
return volume; // 返回体积
}
};
int main()
{
Box box1; // 调用无参构造函数,构造对象box1
cout << box1.get_volume() << endl;
return 0;
}
/*output
调用无参构造函数
1
*/
一般构造函数
class Box
{
private:
double length; // 盒子的长
double width; // 盒子的宽
double height; // 盒子的高
public:
Box() // 无参构造函数
{
length = 1.0;
width = 1.0;
height = 1.0;
}
Box(double _len, double _wid, double _hei) // 一般构造函数1
{
length = _len;
width = _wid;
height = _hei;
}
Box(double _len, double _wid) // 一般构造函数2
{
length = _len;
width = _wid;
height = 1.0;
}
};
-
一般构造函数可以不止一个。
-
一般构造函数通过函数参数来区分。
-
一般构造函数定义对象。
Box box2(参数1,参数二, 。。)
// 测试一般构造函数
#include <iostream>
using namespace std;
class Box
{
private:
double length; // 盒子的长
double width; // 盒子的宽
double height; // 盒子的高
public:
Box() // 无参构造函数
{
length = 1.0;
width = 1.0;
height = 1.0;
printf("无参构造函数被调用\n");
}
Box(double _len, double _wid, double _hei) // 一般构造函数1
{
length = _len;
width = _wid;
height = _hei;
printf("构造函数 Box(double _len, double _wid, double _hei) 被调用\n");
}
Box(double _len, double _wid) // 一般构造函数2
{
length = _len;
width = _wid;
height = 1.0;
printf("构造函数 Box(double _len, double _wid) 被调用\n");
}
public:
double get_volume() // 成员函数(方法),计算Box体积
{
double volume = length * width * height; // 计算体积
return volume; // 返回体积
}
};
int main()
{
Box box1; // 调用默认构造函数
Box box2(2.0, 3.0); // 调用一般构造函数2
Box box3(2.0, 3.0, 4.0); // 调用一般构造函数1
cout << "box1: " << box1.get_volume() << endl;
cout << "box2: " << box2.get_volume() << endl;
cout << "box3: " << box3.get_volume() << endl;
return 0;
}
/*output
无参构造函数被调用
构造函数 Box(double _len, double _wid) 被调用
构造函数 Box(double _len, double _wid, double _hei) 被调用
box1: 1
box2: 6
box3: 24
*/
拷贝构造函数
如果我们需要通过一个现有对象,去构造一个新的相同的对象,此时我们就需要使用到拷贝构造函数。
class Box
{
private:
double length; // 盒子的长
double width; // 盒子的宽
double height; // 盒子的高
public:
Box() // 无参构造函数
{
length = 1.0;
width = 1.0;
width = 1.0;
}
Box(double _len, double _wid, double _hei) // 一般构造函数
{
length = _len;
width = _wid;
height = _hei;
}
Box(const Box& box) // 拷贝构造函数
{
length = box.length;
width = box.width;
height = box.height;
}
};
- 拷贝构造函数的参数是一个常量引用。
// 测试拷贝构造函数
#include <iostream>
using namespace std;
class Box
{
private:
double length; // 盒子的长
double width; // 盒子的宽
double height; // 盒子的高
public:
Box() // 无参构造函数
{
length = 1.0;
width = 1.0;
height = 1.0;
printf("无参构造函数被调用\n");
}
Box(double _len, double _wid, double _hei) // 一般构造函数1
{
length = _len;
width = _wid;
height = _hei;
printf("构造函数 Box(double _len, double _wid, double _hei) 被调用\n");
}
Box(const Box& box) // 拷贝构造函数
{
length = box.length;
width = box.width;
height = box.height;
printf("拷贝构造函数被调用\n");
}
public:
double get_volume() // 成员函数(方法),计算Box体积
{
double volume = length * width * height; // 计算体积
return volume; // 返回体积
}
};
int main()
{
Box box1; // 调用默认构造函数
Box box2(2.0, 3.0, 4.0); // 调用一般构造函数
Box box3(box2); // 调用拷贝构造函数
cout << "box1: " << box1.get_volume() << endl;
cout << "box2: " << box2.get_volume() << endl;
cout << "box3: " << box3.get_volume() << endl;
return 0;
}
/*output
无参构造函数被调用
构造函数 Box(double _len, double _wid, double _hei) 被调用
拷贝构造函数被调用
box1: 1
box2: 24
box3: 24
*/
移动构造函数
- 用一个现有对象去创建一个新对象。
- 一般当对象管理者堆上的属性时,才会用到。
class A
{
private:
int *ptr;
public:
A() {ptr = new int();} // 无参构造函数
A(int *_p) {ptr = _p;}// 一般构造函数
A(const A& _a) // 拷贝构造函数
{
ptr = new int( *(_a.ptr) );
}
A(A&& _a) // 移动构造函数
{
ptr = _a.ptr;
_a.ptr = nullptr;
}
~A() // 析构函数 后面说
{
if (ptr != nullptr)
delete ptr;
}
};
- 看不明白也没关系,这里没有细讲。
- 移动构造函数可以不着急掌握。等后面对类很熟悉后,在了解也不晚,不影响类的使用。
类的析构函数
当我们调用构造函数创建对象时,当超出这个对象作用域时,会调用构造函数将对象析构。
class A
{
private:
int *ptr;
public:
A() {ptr = new int();} // 无参构造函数
A(int *_p) {ptr = _p;}// 一般构造函数
Box(const A& _a) // 拷贝构造函数
{
ptr = new int( *(_a->ptr) );
}
~Box() // 析构函数
{
if (ptr != nullptr)
delete ptr;
}
};
-
A的对象在构造之处,使用new在堆上创建了int数据。我们知道,new出的数据,一定要调用delete删除。
-
c++编译器不会自动调用delete,需要手动调用。所以我们在析构函数进行此操作。
2023/6/17更新
this指针
简单使用
在执行任何成员函数时,该成员函数都自动包含一个隐藏的指针,称为this指针。
this指针指向此成员函数的对象本身,存放的是该成员函数的地址。
比如,我们有时候会写出这样的成员函数。
class A
{
private:
int age;
public:
void setAge(int age) // 设置 age, 函数参数为了好理解, 也同属性名一样的 age
{
// age = age; // 失败
this->age = age;
}
/*
void setAge (int newAge)
{
// do something
int age = 456;
//do somethings
// age = newAge; // 还是不行,编译器分辨不清这是函数中定义的临时变量还是私有属性age。
this->age = newAge; // 正确。 this指向的变量专指属性,不存在任何歧义。
}
*/
void printAge() { cout << age << endl; }
};
为设置私有属性 age 的值,我们需传入新的age值。如果我们把setAge参数名也设置为age。如果不借助this指针,我们只能这样写,age = age, 但测试后发现这样是错误的,无法成功更改age值,因为编译器不知道这两个age分别代表什么。
此时,this也就排上用场了,
this->age = age;
用this->age特指对象的age属性,与传入的age参数区分开来,设置成功。
this指针确实在成员函数中存在,且给我们提供一种没有歧义的设置属性的手段。
函数返回对象指针和引用
this
是指向此对象的指针。 *this
是对象本身。
借助 this指针实现 "方法链"
。类似这种形式 ObjectName.func1().func2().func3()
#include <iostream>
using namespace std;
class Student
{
private:
int age;
char sex;
string name;
public:
Student& setAge (int _age) // 返回对象引用
{
this->age = _age;
return *this;
}
Student & setSex (char _sex)
{
this->sex = _sex;
return *this;
}
Student & setName (const string& _name)
{
this->name = _name;
return *this;
}
void printInfo ()
{
cout << "姓名: " << name << endl;
cout << "性别: " << sex << endl;
cout << "年龄: " << age << endl;
}
};
int main()
{
Student stu;
stu.setAge(19).setName("张三").setSex('M');
stu.printInfo();
return 0;
}
/*output
姓名: 张三
性别: M
年龄: 19
*/
const
const对象和const成员函数
const 变量是不能修改其值的变量。自然,也可以定义类类型的const变量。这种变量称为const对象。构成const对象状态的任何成员变量不能被修改。
也就是说,const 对象的任何成员变量本身是一个 const 变量,因而不能修改。
class Box
{
public:
double length;
double width;
double height;
// 使用默认生成的默认无参构造函数
Box() = default;
// 构造函数
Box (double _len, double _wid, double _hei) : length(_len), width(_wid), height(_hei) {}
// 求体积
double getVolume() { return length * width * height ;}
};
Box类的长宽高是公有的,是可以直接通过对象名修改的。例如
Box b1(1, 2, 3);
b1.length = 1.6;
b1.width = 1.5;
b1.height = 1.8;
如果想创建一个不可被修改的Box对象,可借助const。
const Box b2(1, 2, 3);
cout << b2.length << endl; // ok
// b2.length = 1.6; // error
// b2.width = 1.5; // error
// b2.height = 1.8; // error
同样const也可作用域指针和引用。
Box b1(1, 2, 3);
const Box* ptr = &b1;
// ptr->length = 2.3; // error
void printBox(const Box& box); // 在printBox函数中也不可修改box的值
const 成员函数
上面我们通过 const Box b2(1, 2, 3);
定义了一个Box的const对象,此时我们我们不在能修改成员变量,一定程度上保证了数据的安全性。但也带来了一个问题:getVolume()函数也将不可调用。
为什么?因为编译器并不知道 getVolume() 是否修改了成员变量,此时b2.getVolume();
编译通不过。
解决方案:将getVolume函数设置成const 成员函数。
// 修改后的 getVolume
double getVolume() const // const 关键字标识此函数不修改任何成员变量
{ return length * width * height ;}
// b2.getVolume(); 将能通过编译。
对于const对象,只能调用 const 成员函数。因此,应该将不修改对象的所有成员函数指定为const。