C++面向对象的三大特性为:继承、封装、多态
C++认为万事万物都皆为对象,任何一个对象都应当具有两个要素,即属性和方法
例如:
具有相同性质的对象,我们可以抽象称为类,人属于人类,车属于车类
封装的意义
类的声明与定义:
class Student
{
public:
//定义一个成员函数
void display()
{
cout <<"num:" << num << endl;
cout <<"name:" << name << endl;
cout <<"sex:" << sex << endl;
}
private:
// 声明三个成员变量
int num;
char name[20];
char sex;
};
可以看到,声明类的方法是由声明结构体类型的、的方法发展而来的,一般把数据隐藏起来,而把成员函数作为对外界的接口。
class 类名
{
private:
属性 / 行为
public:
属性 / 行为
};
除private和public外,还有protected权限,class类的默认权限为private其中三种权限数据的访问范围如下表:
权限 | 数据访问空间 |
---|---|
private |
类内可以访问 类外不可以访问,可被派生类访问(与protected 的区别) |
public |
类内可以访问 类外可以访问 |
protected |
类内可以访问 类外不可以访问 |
实例化对象
实例化2个Student 类的对象stu1与stu2
Student stu1,stu2;
成员对象的引用
对象名 . 成员名
stu1.num = 100;
stu1.display();
Student *p // 定义指针p
p = &stu1; // p指向对象stu1
cout << p->num << endl;
Student &t = stu1; // 定义Student 类的的引用t,并初始化为stu1
cout << t.num << endl;
成员函数
在类的内部对函数做声明,而在类外定义函数,这是程序设计的一种良好的习惯。这样不仅可以减少类体的长度,使类体清晰,便于阅读,而且能使类的接口和类的实现细节分离。
class Student
{
public:
//定义一个成员函数
void display();
private:
// 声明三个成员变量
int num;
char name[20];
char sex;
};
void Student :: display()
{
cout <<"num:" << num << endl;
cout <<"name:" << name << endl;
cout <<"sex:" << sex << endl;
}
问题引入:怎么对成员变量初始化?
以下初始化是错误的:
class Student
{
private:
string name="zhangsan1";
int age=21;
char sex='m';
int score=100;
};
这种初始化方法显然是错误的,因为类并不是一个实体,而是一种抽象类型,并不占存储空间,显然无处容纳数据。
为了解决这个问题,C++提供了构造函数(constructor)来处理对象的初始化。构造函数是一种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,而是在建立对象时自动执行,其中函数名称与类名相同。
例如:
Student::Student() //无参构造,默认
{
name = "zhangsan";
age = 20;
sex = 'm';
score = 100;
}
构造函数语法:类名(){}
构造函数特点:
经验:通常在构造函数的声明时候指定默认参数,而不能只在定义函数时候指定默认参数
典例:已知长方体的长宽高,求长方体体积
#include
using namespace std;
class Box
{
public:
Box(int h=10, int w=10, int l=10); //声明构造函数带默认参数
void getVolume();
private:
int height;
int width;
int length;
};
int main()
{
Box box1; // 10 10 10
Box box2(15); // 15 10 10
Box box3(15,20); // 15 20 10
Box box4(15,20,30); // 15 20 30
box1.getVolume();
box2.getVolume();
box3.getVolume();
box4.getVolume();
return 0;
}
//构造函数实现体
Box::Box(int h, int w, int l) //有参构造
{
height = h;
width = w;
length = l;
}
void Box::getVolume()
{
int volume = height*width*length;
cout << "volume=" << volume << endl;
}
在上述构造中主要涉及的为普通构造,普通构造有分为无参构造和有参构造,在C++中还存在一种拷贝构造,即构造函数传入的参数是一个类的引用,将传入参数类的属性拷贝到当前类。
拷贝构造的定义:
//拷贝构造函数实现
Person::Person(const Person &p) //拷贝构造 记住这种写法就行
{
//将传入的人身上的所有属性,拷贝到我身上
name = p.name;
age = p.age;
sex = p.sex;
cout << "Person的拷贝构造函数调用" << endl;
}
调用:
Person p2(p1);
在这之前,需要对p1对象进行实例化,并通过有参数构造初始化p1的值,其中p1对象的有参构造函数如下:
//有参构造实现
Person::Person(string name, int age, char sex)
{
this->name = name;
this->age = age;
this->sex = sex;
cout << "Person的有参构造函数调用" << endl;
}
不难看出,在这里Person构造函数发生重载,通过
Person p1("zhangsan",20,'m');
先初始化p1,通过有参构造进行赋值,再通过进行拷贝构造,自动调用拷贝构造函数,则完整程序如下所示:
#include
#include
using namespace std;
class Person
{
public:
//构造函数发送函数重载
//有参构造
Person(string name, int age, char sex);
//拷贝构造函数
Person(const Person &p); //拷贝构造 记住这种写法就行
void show_info(); //显示信息
private:
string name;
int age;
char sex;
};
//有参构造实现
Person::Person(string name, int age, char sex)
{
this->name = name;
this->age = age;
this->sex = sex;
cout << "Person的有参构造函数调用" << endl;
}
//拷贝构造函数实现
Person::Person(const Person &p) //拷贝构造 记住这种写法就行
{
//将传入的人身上的所有属性,拷贝到我身上
name = p.name;
age = p.age;
sex = p.sex;
cout << "Person的拷贝构造函数调用" << endl;
}
void Person:: show_info()
{
cout << "name=" << name;
cout << "\tage=" << age;
if(sex = 'm')
cout << "\tsex=男" << endl;
else
cout << "\tsex=女" << endl;
}
int main()
{
Person p1("zhangsan",20,'m');
Person p2(p1);
p1.show_info();
p2.show_info();
return 0;
}
程序运行结果:
Person的有参构造函数调用
Person的拷贝构造函数调用
name=zhangsan age=20 sex=男
name=zhangsan age=20 sex=男
整理/补充
1. 构造函数两种分类方式:
2. 构造函数调用规则
在初始化成员属性时候,C++还提供了一种初始化数据的方法——初始化成员列表,其基本语法为:
类名::构造函数名([参数表])[:初始化成员表]
{
[构造函数体]
}
例:
Person(string name_arg, int age_arg, char sex_arg): name(name_arg), age(age_arg), sex(sex_arg){}
说明:如果数据成员是数组, 则应当在构造函数的函数体中用语句对其赋值, 而不能在参数初始化表中对其初始化。
析构函数语法:~类名(){}
析构函数的特点:
析构函数的作用:
析构函数的作用在于撤销对象占用的内存之前完成一些清理工作。
实际上,析构函数的作用并不仅限于释放资源方面, 它还可以被用来执行"用户希望在最后一次使用对象之后所执行的任何操作", 例如输出有关的信息。
构造与析构的顺序:
先构造的后析构,后构造的先析构
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
数组不仅可以由简单变量组成(例如整型数组的每一个元素都是整型变量),也可以由类对象组成(对象数组的每一个元素都是同类的对象)。
//假设已经声明了Student类,定义stu数组,有50个元素
Student stu[50];
如何初始化对象数组:
Student stu[3]={60,70,80};
Student stu[3]={
Student("zhangsan",18,87), //调用第一个元素的构造函数,提供3个实参
Student("lisi",19,76), //调用第二个元素的构造函数,提供3个实参
Student("wangwu",18,72) //调用第三个元素的构造函数,提供3个实参
};
其中3个参数分别代表姓名、年龄和分数。在建立对象时,编译系统会为每一个对象分配一定的存储空间,以存放其数据成员。对象空间的起始地址就是对象的指针。可以定义一个指针变量,用来存放对象的地址,这就是指向对象的指针变量。
设假定已定义Student类,在此基础上有以下语句:
Student *pt; //定义pt为指向Student类的类对象指针
Student stu1; //实例化对象stu1
pt = &stu1; //将t1的起始地址赋给pt
定义指向类对象的指针变量的一般形式为:
类名 * 对象指针名
可以通过对象指针访问对象和对象的成员:
*pt //pt所指向的对象,即stu1
(*pt).age //pt所指向的对象的age成员,即stu1.age
pt->name; //pt所指向的对象的name成员,即stu1.name
(*pt).showInfo(); //调用pt所指向对象的showInfo函数,即stu1.showInfo()
pt->showInfo(); //调用pt所指向对象的showInfo函数,即stu1.showInfo()
对象有地址,存放对象的起始地址的指针变量就是指向对象的指针变量。对象成员也有地址,存放对象成员地址的指针变量就是指向对象成员的指针变量。
1. 指向对象数据成员的指针
int *p1 //指向对象成员的指针变量
p1 = &stu1.age;
cout << * p1 << endl;
2. 指向对象成员函数的指针
定义指向对象成员函数的指针变量的方法和定义指向普通函数的指针变量方法有所不同。
指针变量的类型必须与赋值号右侧函数的类型像匹配,要求在以下3方面都要匹配:
这里重温指向普通函数的指针变量的定义方法::
void (*p)() //p是指向void 型函数的指针变量
p = fun(); //将fun函数的入口地址给p,p指向fun函数
(*p)(); //调用fun函数
而对于一个对象成员函数的指针变量如下所示:
定义p2指向Student类中公用成员函数的指针变量
void (Student::*p2)();
定义公用成员函数的指针变量一般形式为:
数据类型名 (类名::* 指针变量名)(参数列表)
初始化成员函数指针变量:
p2 = Student::showInfo;
一般形式为:
指针变量名 = & 类名 :: 成员函数名
对象指针典例:
int main()
{
Student stu1("zhangsan",22,90);
int *p1 = &stu1.age; //定义成员数据指针
cout << *p1 << endl;
Student *p2 = &stu1; //定义类指针
p2->showInfo();
void (Student::*p3)(); //定义成员函数指针
p3 = &Student::showInfo;
(stu1.*p3)();
return 0;
}
在每一个成员函数中都包含一个特殊的指针, 这个指针的名字是固定的, 称为 this。它是指向本类对象的指针, 它的值是当前被调用的成员函数所在的对象的起始地址。
this指针是隐式使用的,它是作为参数被传递给成员函数的,但有时也需要显示使用。
Student::Student(string name, int age, int score)
{
this->name = name;
this->age = age;
this->score = score;
}
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
典例:用立方体类Box定义两个对象,引用不同对象中的静态数据成员
#include
using namespace std;
class Box{
public:
Box(int w, int l);
int getVolume();
static int height; //把 height 定义为公用的静态数据成员
private:
int width;
int length;
};
int Box:: height = 10; //静态成员类外初始化
Box::Box(int w, int l)
{
width = w;
length = l;
}
//计算体积函数体
int Box::getVolume()
{
int volume;
volume = height * width * length;
return volume;
}
int main()
{
Box box1(15,20);
Box box2(20,30);
cout << box1.height << endl; //通过对象box1引用静态数据成员height
cout << box2.height << endl; //通过对象box2引用静态数据成员height
cout << Box::height << endl; //通过类名引用静态数据成员height
//计算体积
cout << "volume=:" << box1.getVolume() << endl;
return 0;
}
说明:
(1)上面的程序中,将height定义为公用的静态数据成员。
(2)可以看到,在类外可以通过对象名访问公用的静态数据成员,也可以通过类名引用静态数据成员。
Box::getVolume();
实际上也允许通过对象名调用静态成员变量,如:a.getVolume();
但这不代表函数属于对象a,只是用a的类型而已典例:统计学生平均成绩,使用静态成员函数。
#include
using namespace std;
class Student{
public:
Student(string, int, int);
void get_total();
static float get_average();
private:
string name;
int age;
int score;
static int sum; //总分
static int count; //人数
};
//静态成员属性类外声明
int Student::sum = 0;
int Student::count = 0; //计数
Student::Student(string name_temp, int age_temp, int score_temp)
{
name = name_temp;
age = age_temp;
score = score_temp;
}
void Student::get_total()
{
sum = sum + score;
count++; //定义已统计的人数
}
float Student::get_average()
{
return (sum/(float)count);
}
int main()
{
Student stu[3]={
Student("zhangsan",18,87),
Student("lisi",19,76),
Student("wangwu",18,72),
};
int i,n;
cout << "please input the number of students:";
cin >> n;
for(i=0;i<n;i++)
stu[i].get_total();
cout << "the average score of" << n << "student is:" << Student::get_average() << endl;
return 0;
}
说明:
(1)在类中定义两个静态数据成员sun和count,这是因由于这两个数据成员的值需要进行累加,并不属于某一个对象元素,而是各个元素共享的,可以看到他们的值在不断变化,始终不释放内存空间。
(2)get_total属于公用的成员函数,公用的成员函数可以访问对象的一般数据成员和静态数据成员。
(3)get_average属于静态成员函数,静态成员函数只能访问静态数据成员。
思考:如何在get_total中访问stu的数据呢?
A:将对象stu的首地址作为get_total的参数。,即get_total的参数为类指针 Student *p
void get_total(Student *stu);
void Student::get_total(Student *stu)
{
sum = sum + stu->score;
cout << stu->score << endl;
count++; //定义已统计的人数
}
for(i=0;i<n;i++)
stu[i].get_total(&stu[i]);
如果在本类以外的其他地方定义了一个函数(这个函数可以是不属于任何类的非成员函数,也可以是其他类的成员函数), 在类体中用friend对其进行声明此函数就称为本类的友元函数,友元函数可以访问这个类中的私有成员。
友元目的:让一个函数或者类访问另一个类中私有成员,该函数可以为全局函数,也可以为其他类的成员函数
友元语法:friend 函数声明;
在上述的函数可以是全局的普通函数,例如:显示时间
#include
using namespace std;
class Time{
public:
Time(int h, int m, int s):hour(h),min(m),sec(s){};
friend void dispaly(Time *t); //将display作为类的友元函数, 使得display能够访问类的私有成员
private:
int hour;
int min;
int sec;
};
void dispaly(Time *t)
{
cout << t->hour << ":" << t->min << ":" << t->sec << endl;
}
int main()
{
Time t1(10,17,34);
dispaly(&t1);
return 0;
}
程序第7行将display作为类的友元函数, 使得全局函数display能够访问类的私有成员
friend 函数不仅可以是一般函数(非成员函数), 而且可以是另一个类中的成员函数。
#include
using namespace std;
class Date;
class Time{
public:
Time(int h, int m, int s):hour(h),min(m),sec(s){};
void display(Date &);
private:
int hour;
int min;
int sec;
};
class Date{
public:
Date(int y, int m, int d):year(y),month(m),day(d){};
//友元Time类中的成员函数 display,使其访问该类中的私有成员
friend void Time::display(Date &date);
private:
int year;
int month;
int day;
};
void Time::display(Date &date)
{
cout << date.year << "/" << date.month << "/" << date.day << endl;
cout << hour << ":" << min << ":" << sec << endl;
}
int main()
{
Date date(2023,11,9);
Time time(11,5,23);
time.display(date);
return 0;
}
注意在本程序中调用友元函数访问有关类的私有数据方法:
程序说明:
不仅可以将一个函数声明为一个类的“朋友”,而且可以将一个类(例如B类)声明为另一个类(例如A类)的“朋友”。这时B类就是A类的友元类。友元类B中的所有函数都是A类的友元函数, 可以访问A类中的所有成员。
声明友元类类的一般形式为:
friend 类名;
说明: