类和对象
我们编写程序的目的是为了解决现实中的问题,而这些问题的构成都是由各种事物组成,我们在计算机中要解决这种问题,首先要做就是要将这个问题的参与者(事和物)抽象到计算机程序中,也就是用程序语言表示现实的事物。
那么现在问题是如何用程序语言来表示现实事物?现实世界的事物所具有的共性就是每个事物都具有自身的属性,一些自身具有的行为,所以如果我们能把事物的属性和行为表示出来,那么就可以抽象出来这个事物。
比如我们要表示人这个对象,在C语言中我们可以这么表示:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct _Person {
char name[64];
int age;
}Person;
typedef struct _Aninal {
char name[64];
int age;
int type; //动物种类
}Ainmal;
void PersonEat(Person* person) {
printf("%s在吃人吃的饭!\n", person->name);
}
void AnimalEat(Ainmal* animal) {
printf("%s在吃动物吃的饭!\n", animal->name);
}
int main(int argc, char *argv[]) {
Person person;
strcpy(person.name, "小明");
person.age = 30;
AnimalEat(&person);
return 0;
}
程序输出:
小明在吃动物吃的饭!
定义一个结构体用来表示一个对象所包含的属性,函数用来表示一个对象所具有的行为,这样我们就表示出来一个事物。在C语言中,行为和属性是分开的,也就是说吃饭这个属性不属于某类对象,而属于所有的共同的数据,所以不单单是PeopleEat
可以调用Person
数据,AnimalEat
也可以调用Person
数据,那么万一调用错误,将会导致问题发生。
从这个案例我们应该可以体会到,属性和行为应该放在一起,一起表示一个具有属性和行为的对象。
C和C++中struct区别
- C语言中
struct
只能包含变量 - C++语言
struct
既能包含变量,也能包含函数
类的封装
假如某对象的某项属性不想被外界获知,比如说漂亮女孩的年龄不想被其他人知道,那么年龄这条属性应该作为女孩自己知道的属性;或者女孩的某些行为不想让外界知道,只需要自己知道就可以。那么这种情况下,封装应该再提供一种机制能够给属性和行为的访问权限控制住。
所以说:一个是属性和变量合成一个整体,一个是给属性和函数增加访问权限。
封装
- 把变量(属性)和函数(操作)合成一个整体,封装在一个类中
- 对变量和函数进行访问控制
访问权限
- 在类的内部(作用域范围内),没有访问权限之分,所有成员可以相互访问
- 在类的外部(作用域范围外),访问权限才有意义:
public
,private
,protected
- 在类的外部,只有
public
修饰的成员才能被访问,在没有涉及继承与派生时,private
和protected
是同等级的,外部不允许访问

代码演示
#include <iostream>
using namespace std;
// 封装两层含义
// 1. 属性和行为合成一个整体
// 2. 访问控制,现实事物本身有些属性和行为是不对外开放
class Person {
// 人具有的行为(函数)
public:
void Dese() { cout << "我有钱,年轻,个子又高,就爱嘚瑟!" << endl; }
// 人的属性(变量)
public:
int mTall; // 多高,可以让外人知道
protected:
int mMoney; // 有多少钱,只能儿子孙子知道
private:
int mAge; // 年龄,不想让外人知道
};
int main(int argc, char *argv[]) {
Person p;
p.mTall = 220;
// p.mMoney 保护成员外部无法访问
// p.mAge 私有成员外部无法访问
p.Dese();
return 0;
}
程序输出:
我有钱,年轻,个子又高,就爱嘚瑟!
struct和class的区别
注意
class默认访问权限为private,struct默认访问权限为public。
class A {
int mAge;
};
struct B {
int mAge;
};
void test() {
A a;
B b;
//a.mAge; // 无法访问私有成员
b.mAge; // 可正常外部访问
}
将成员变量设置为private
可赋予客户端访问数据的一致性
如果成员变量不是
public
,客户端唯一能够访问对象的方法就是通过成员函数。如果类中所有public
权限的成员都是函数,客户在访问类成员时只会默认访问函数,不需要考虑访问的成员需不需要添加()
,这就省下了许多搔首弄耳的时间。可细微划分访问控制
使用成员函数可使得我们对变量的控制处理更加精细。如果我们让所有的成员变量为
public
,每个人都可以读写它。如果我们设置为private
,那么我们就可用通过相应的get/set成员函数实现“不准访问”、“只读访问”、“读写访问”,甚至你可以写出“只写访问”。
class AccessLevels {
public:
//对只读属性进行只读访问
int getReadOnly() { return readOnly; }
//对读写属性进行读写访问
void setReadWrite(int val) { readWrite = val; }
int getReadWrite() { return readWrite; }
//对只写属性进行只写访问
void setWriteOnly(int val) { writeOnly = val; }
private:
int readOnly; //对外只读访问
int noAccess; //外部不可访问
int readWrite; //读写访问
int writeOnly; //只写访问
};
成员变量和函数的存储
在C语言中,变量和函数分开来声明的。也就是说,语言本身并没有支持“数据”和“函数”之间的关联性。我们把这种程序方法称为“程序性的”,由一组“分布在各个以功能为导航的函数中”的算法驱动,它们处理的是共同的外部数据。
C++实现了“封装”,那么数据(成员属性)和操作(成员函数)是什么样的呢?
“数据”和“处理数据的操作(函数)”是分开存储的。
- C++中的非静态数据成员直接内含在类对象中,就像C中的struct一样。
- 成员函数(member function)虽然内含在class声明之内,却不出现在对象中。
- 每一个非内联成员函数(non-inline member function)只会诞生一份函数实例。
代码演示
#include <iostream>
using namespace std;
class TestClass00 {
};
class TestClass01 {
public:
int mA;
};
class TestClass02 {
public:
int mA;
static int sB;
};
class TestClass03 {
public:
void printTestClass() {
cout << "hello world!" << endl;
}
public:
int mA;
static int sB;
};
class TestClass04 {
public:
void printTestClass() {
cout << "hello world!" << endl;
}
static void ShowTestClass() {
cout << "hello world!" << endl;
}
public:
int mA;
static int sB;
};
int main(int argc, char *argv[]) {
TestClass00 tclass00;
TestClass01 tclass01;
TestClass02 tclass02;
TestClass03 tclass03;
TestClass04 tclass04;
cout << "TestClass00:" << sizeof(tclass00) << endl; // 1
cout << "TestClass01:" << sizeof(tclass01) << endl; // 4
// 静态数据成员并不保存在类对象中
cout << "TestClass02:" << sizeof(tclass02) << endl; // 4
// 非静态成员函数不保存在类对象中
cout << "TestClass03:" << sizeof(tclass03) << endl; // 4
// 静态成员函数也不保存在类对象中
cout << "TestClass04:" << sizeof(tclass04) << endl; // 4
return 0;
}
程序输出:
TestClass00:1
TestClass01:4
TestClass02:4
TestClass03:4
TestClass04:4
通过上面的案例,我们可以的得出:
另外一个值得注意的点是。
通过上例我们知道,C++的数据和操作也是分开存储,并且每一个非内联成员函数(non-inline member function)只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
这一块代码是如何区分那个对象调用自己的呢?
C++通过提供特殊的对象指针——this指针,解决上述问题。this指针指向被调用的成员函数所属的对象。
this指针
C++规定,this指针是隐含在对象成员函数内的一种指针。当一个对象被创建后,它的每一个成员函数都含有一个系统自动生成的隐含指针this,用以保存这个对象的地址,也就是说虽然我们没有写上this指针,编译器在编译的时候也是会加上的。因此this也称为“指向本对象的指针”,this指针并不是对象的一部分,不会影响 sizeof(对象)
的结果。
this指针是C++实现封装的一种机制,它将对象和该对象调用的成员函数连接在一起,在外部看来,每一个对象都拥有自己的函数成员。一般情况下,并不写this,而是让系统进行默认设置。
成员函数通过this指针即可知道操作的是哪个对象的数据。this指针无需定义,直接使用即可。
注意
静态成员函数内部没有this指针,静态成员函数不能操作非静态成员变量。
C++编译器对普通成员函数的内部处理
编译器会给所有的成员函数添加一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。

this指针的使用
- 当形参和成员变量同名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用
return *this;
代码演示
#include <iostream>
using namespace std;
class Person {
public:
//1. 当形参名和成员变量名一样时,this指针可用来区分
Person(string name, int age) {
//name = name;
//age = age; //输出错误
this->name = name;
this->age = age;
}
//2. 返回对象本身的引用
//重载赋值操作符
//其实也是两个参数,其中隐藏了一个this指针
Person PersonPlusPerson(Person& person) {
string newname = this->name + person.name;
int newage = this->age + person.age;
Person newperson(newname, newage);
return newperson;
}
void ShowPerson() {
cout << "Name:" << name << " Age:" << age << endl;
}
public:
string name;
int age;
};
//3. 成员函数和全局函数(Perosn对象相加)
Person PersonPlusPerson(Person& p1, Person& p2) {
string newname = p1.name + p2.name;
int newage = p1.age + p2.age;
Person newperson(newname, newage);
return newperson;
}
int main(int argc, char *argv[]) {
Person person("John", 100);
person.ShowPerson();
cout << "---------" << endl;
Person person1("John", 20);
Person person2("001", 10);
//1.全局函数实现两个对象相加
Person person3 = PersonPlusPerson(person1, person2);
person1.ShowPerson();
person2.ShowPerson();
person3.ShowPerson();
//2. 成员函数实现两个对象相加
Person person4 = person1.PersonPlusPerson(person2);
person4.ShowPerson();
return 0;
}
程序输出:
Name:John Age:100
---------
Name:John Age:20
Name:001 Age:10
Name:John001 Age:30
Name:John001 Age:30
使用空指针访问成员函数
当该对象的指针指向了空的时间,是无法使用到this指针的。此时没有用到this指针的成员函数是可以被成功调用的;但如果成员函数用到了this指针(也就是在这个成员函数内部操作成员属性了),调用这个成员函数会导致程序出错。
如果成员函数没有用到this指针,可以用空指针调用成员函数
#include <iostream>
using namespace std;
class Person {
public:
void PrintClass() {
cout << "没有使用this指针的成员函数" << endl;
}
private:
int m_Age;
};
int main(int argc, char *argv[]) {
Person* p = NULL;
p->PrintClass();
return 0;
}
程序输出:
没有使用this指针的成员函数
如果成员函数用到了this指针,需要在成员函数头部加入this指针的判空逻辑代码以提高程序的健壮性
#include <iostream>
using namespace std;
class Person {
public:
void PrintClass() {
cout << "没有使用this指针的成员函数" << endl;
}
void PrintAge() {
//m_Age = 10;
cout << m_Age << endl;
}
private:
int m_Age;
};
int main(int argc, char *argv[]) {
Person* p = NULL;
p->PrintAge();
return 0;
}
程序无法正常运行,并在第12行报错“引发了异常: 读取访问权限冲突。this 是 nullptr。”

此时,就需要在PrintAge成员函数头部添加this指针的判空逻辑来提高程序的健壮性
void PrintAge() {
if (this == NULL) {
cout << "this指针为空,已取消该函数调用" << endl;
return;
}
m_Age = 10;
cout << m_Age << endl;
}
再次使用空指针调用修改后的PrintAge成员函数,程序可以正常运行并输出“this指针为空,已取消该函数调用”。
面向对象程序设计案例
判断立方体是否相等
设计立方体类(Cube),求出立方体的面积( 2*a*b + 2*a*c + 2*b*c )和体积( a * b * c),分别用全局函数和成员函数判断两个立方体是否相等。
代码实现
#include <iostream>
using std::cout;
using std::endl;
class Cubes {
public:
Cubes(int l, int w, int h) : mL(l), mW(w), mH(h) {}
// 获取长
int getL() const { return mL; }
// 设置长
void setL(int l) { mL = l; }
// 获取宽
int getW() const { return mW; }
// 设置宽
void setW(int w) { mW = w; }
// 获取高
int getH() const { return mH; }
// 设置高
void setH(int h) { mH = h; }
// 计算立方体的面积
int getArea() const { return 2 * mL * mW + 2 * mL * mH + 2 * mW * mH; }
// 计算立方体的体积
int getVolume() const { return mL * mW * mH; }
// 使用成员函数判断两个立方体是否相等
bool isEqual(const Cubes &other) {
if (mL == other.getL() && mW == other.getW() && mH == other.getH()) {
return true;
}
return false;
}
private:
int mL; // 长
int mW; // 宽
int mH; // 高
};
// 使用全局函数判断两个立方体是否相等
bool isEqual(const Cubes &c1, const Cubes &c2) {
if (c1.getL() == c2.getL() && c1.getW() == c2.getW() && c1.getH() == c2.getH()) {
return true;
}
return false;
}
int main() {
Cubes c1(10, 5, 8);
Cubes c2(10, 5, 8);
Cubes c3(10, 6, 8);
cout << "c1 Area is " << c1.getArea() << " and Volume is " << c1.getVolume() << endl;
cout << "c2 Area is " << c2.getArea() << " and Volume is " << c2.getVolume() << endl;
cout << "c3 Area is " << c3.getArea() << " and Volume is " << c3.getVolume() << endl;
cout << (c1.isEqual(c2) ? "c1和c2相等" : "c1和c2不相等") << endl;
cout << (c1.isEqual(c3) ? "c1和c3相等" : "c1和c3不相等") << endl;
cout << (isEqual(c1, c2) ? "c1和c2相等" : "c1和c2不相等") << endl;
cout << (isEqual(c2, c3) ? "c2和c3相等" : "c2和c3不相等") << endl;
return 0;
}
程序输出:
$ ./main
c1 Area is 340 and Volume is 400
c2 Area is 340 and Volume is 400
c3 Area is 376 and Volume is 480
c1和c2相等
c1和c3不相等
c1和c2相等
c2和c3不相等
点和圆的关系
设计一个圆形类(AdvCircle),和一个点类(Point),计算点和圆的关系。
假如圆心坐标为x0, y0, 半径为r,点的坐标为x1, y1:
- 点在圆上:(x1-x0)*(x1-x0) + (y1-y0)*(y1-y0) == r*r
- 点在圆内:(x1-x0)*(x1-x0) + (y1-y0)*(y1-y0) < r*r
- 点在圆外:(x1-x0)*(x1-x0) + (y1-y0)*(y1-y0) > r*r
代码实现
#include <iostream>
using namespace std;
//点类
class Point {
public:
void setX(int x) { mX = x; }
void setY(int y) { mY = y; }
int getX() { return mX; }
int getY() { return mY; }
private:
int mX;
int mY;
};
//圆类
class Circle {
public:
void setP(int x, int y) {
mP.setX(x);
mP.setY(y);
}
void setR(int r) { mR = r; }
Point& getP() { return mP; }
int getR() { return mR; }
//判断点和圆的关系
void IsPointInCircle(Point& point) {
int distance = (point.getX() - mP.getX()) * (point.getX() - mP.getX()) + (point.getY() - mP.getY()) * (point.getY() - mP.getY());
int radius = mR * mR;
if (distance < radius) {
cout << "Point(" << point.getX() << "," << point.getY() << ")在圆内!" << endl;
}
else if (distance > radius) {
cout << "Point(" << point.getX() << "," << point.getY() << ")在圆外!" << endl;
}
else {
cout << "Point(" << point.getX() << "," << point.getY() << ")在圆上!" << endl;
}
}
private:
Point mP; //圆心
int mR; //半径
};
void test() {
//实例化圆对象
Circle circle;
circle.setP(20, 20);
circle.setR(5);
//实例化点对象
Point point;
point.setX(25);
point.setY(20);
circle.IsPointInCircle(point);
}
int main(int argc, char *argv[]) {
test();
return 0;
}
程序输出:
Point(25,20)在圆上!
分文件实现
对于第二个案例,类的声明和类的定义分别在.h和.cpp实现。
#ifndef POINT_H
#define POINT_H
class Point {
public:
Point();
Point(int x, int y);
void setX(int x);
void setY(int y);
int getX(void);
int getY(void);
private:
int x;
int y;
};
#endif// POINT_H
#include "point.h"
Point::Point() {
this->x = 0;
this->y = 0;
}
Point::Point(int x, int y) {
this->x = x;
this->y = y;
}
void Point ::setX(int x) {
this->x = x;
}
void Point::setY(int y) {
this->y = y;
}
int Point::getX(void) {
return this->x;
}
int Point::getY(void) {
return this->y;
}
#ifndef CIRCLE_H
#define CIRCLE_H
#include "point.h"
class Circle {
public:
Circle();
Circle(Point &p, int r);
void compare(Point &p, Circle &c);
private:
Point p;
int r;
};
#endif// CIRCLE_H
#include "circle.h"
#include <iostream>
Circle::Circle() {
this->r = 0;
}
Circle::Circle(Point &p, int r) {
this->p = p;
this->r = r;
}
void Circle::compare(Point &p, Circle &c) {
if ((p.getX() - c.p.getX()) * (p.getX() - c.p.getX()) + (p.getY() - c.p.getY()) * (p.getY() - c.p.getY()) == r * r) {
std::cout << "点在圆上" << std::endl;
} else if ((p.getX() - c.p.getX()) * (p.getX() - c.p.getX()) + (p.getY() - c.p.getY()) * (p.getY() - c.p.getY()) < r * r) {
std::cout << "点在圆内" << std::endl;
} else {
std::cout << "点在圆外" << std::endl;
}
}
#include <iostream>
#include "point.h"
#include "circle.h"
using namespace std;
int main(int argc, char *argv[]) {
Point p1(20, 20);
Point p2(25, 20);
Circle c(p1, 5);
c.compare(p2, c);
return 0;
}
运行结果
课堂练习
请设计一个Person
类,Person
类具有name
和age
属性,提供初始化函数(Init
),并提供对name
和age
的读写函数(set
,get
),但必须确保age
的赋值在有效范围内(0-100),超出有效范围则拒绝赋值,并提供方法输出姓名和年龄。(10分钟)
点击查看答案
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
void Init(int age, string name) {
if (age < 0 || age > 100) {
cout << "年龄输入错误" << endl;
return;
} else {
this->m_age = age;
this->name = name;
}
}
void setage(int age) {
if (age < 0 || age > 100) {
cout << "年龄输入错误" << endl;
return;
} else {
this->m_age = age;
}
}
void setname(string name) { this->name = name; }
int getage(void) { return this->m_age; }
string getname(void) { return this->name; }
private:
int m_age;
string name;
};
int main(int argc, char *argv[]) {
Person p;
p.Init(18, "张三");
cout << "进行初始化后,姓名:" << p.getname() << "\t年龄:" << p.getage() << endl;
p.setage(15);
cout << "进行设置年龄后,姓名:" << p.getname() << "\t年龄:" << p.getage() << endl;
p.setname("李四");
cout << "进行设置姓名后,姓名:" << p.getname() << "\t年龄:" << p.getage() << endl;
p.setage(101);
cout << "进行错误的设置年龄后,姓名:" << p.getname() << "\t年龄:" << p.getage() << endl;
return 0;
}