可学习内容:
- 理解抽象数据类型的含义和作用;
- 掌握定义类·数据成员·成员函数的语法;
- 理解封装和信息隐藏的必要性
- 理解访问限定符的作用
- 理解this指针的含义和用途
- 理解访问器和修改器的用途
7.理解friend的含义和作用
8.理解构造函数和析构函数的作用
9 掌握定义构造函数和析构函数的语法
10 理解委托构造函数的语法和用途
11 理解const成员的含义和作用
12 理解static成员的含义和用途
前言: 类的基本思想:1. 数据抽象 2. 封装。
1.1 抽象数据类型(两部分组成) 组成:一组数据和对这些数据的操作。
例如:结构体加上全局函数。
结构体表示数据,全局函数表示对这些数据的操作,数据则以参数的形式传递给函数。
1.2 数据成员与成员函数结构体内的函数被称为成员函数结构体中的数据则被称为数据成员这样的结构体被称为类,这种结构体类型的变量被称为对象。
1.3 数据成员的类内初始化
1.4 成员函数的类外定义 成员函数可以在类内定义,也可以类外定义。
现在介绍类外定义情况:要求:先类内声明(先打个招呼),后类外补充定义(具体对数据的操作内容)。
C++中每个类定义都引入了一个类作用域,而类定义中声明的数据成员和成员函数都具有类作用域。 具体操作:
成员函数在类外定义时,函数名字前要加类名字和作用域符“::”,表示这个函数实在其所属的类作用域内,是这个类的成员函数,不同于全局函数。如果只是访问(操作)类的成员,类内定义与类外定义没什么差别。
二.访问控制和封装
2.1 信息隐藏的必要性
2.2 访问限定符
C++通过限定成员的访问权限来设置边界(权限),实现信息隐藏。
关键字:public,private,protected被称为访问限定符。
访问限定符在定义中的出现顺序与次数没有限制。一个访问限定符的作用会持续到出现下一个访问限定符或类定义结束。
访问限定符在类定义中使用,一般语法如下:
例:
struct 类名{
public: 公有成员声明;
private: 私有成员声明;
protected: 被保护成员声明;
};
注意分号与冒号!!! 目前只需学习public与private即可。
public成员在程序的任何函数或类中都可以被访问。
private成员只能由类自己的成员函数或友元访问,如果要对某些数据进行隐藏,应该声明为private。
2.3 封装
将数据和操作捆绑在一起,并加上访问控制,这在面向对象中称为封装。
对象是数据和操作的封装体;数据描述对象的属性,操作描述对象的行为。
类是一组具有相同属性和行为的对象的抽象,对象又称类的实例。在C++中,类是一种数据类型,对象是这种类型的变量。发送消息就是调用成员函数,
例如: s.print()就是向对象s发送一个print()消息。
除了使用struct来定义类,还可以使用class来定义类。
区别:如果没有指定访问限定符时,struct成员的默认访问限制为public,而class成员则默认为private。
2.4 this指针
每个成员函数都有一个隐含的参数,指向接受消息的对象,称为this指针。
须知:this指针是一个常量,含有当前实施调用的对象的地址。不能改变this指针的值,也不能取this指针的地址。
那this指针在成员函数中可以显化吗?
答案是可以。
如: class X{
int m;
public:
void setVal(int v) { this -> m=v;}
void inc(int d) { this -> m+=d;}
void changVal(int v) { this ->setVal(v); }
};
但这代码中this的使用不是必需的。
在成员函数中this常见的作用:
- 区别与局部变量重名的数据成员;
- 返回当前对象;
- 获取当前对象的地址;
示范代码:
class X{
int m;
public:
void setVal(int m){
// 作用1
this -> m=m; //区别与函数参数重名的数据成员;
}
X& add(const X& a) {
m += a.m;
// 作用2
return *this; //返回当前对象;
}
void copy(const X& a){ //复制对象;
// 作用3
if(this==&a) //判断当前对象和a是否为同一对象,
return; //相同则无需复制;
m =a.m; //复制操作;
}
};
注意!!!代码中判断两个对象是否相同不是比较它们的属性值是否相等,而是计较它们的内存地址是否相等。
2.5 访问器与修改器
为啥会有访问器与修改器?
类的数据成员一般限定为private,以此杜绝外部对类数据的任意访问。但有些数据是外部需要取得或修改的。如果因此就将数据成员限定为public,显然会破坏对象的封装性。
解决方法:可以将数据成员限定为private,并提供public成员函数来对其进行访问。 而这种成员函数被称为访问器,修改器。
数据成员XX的访问器函数一般命名为getXX,修改器函数命名为setXX。
个人理解: 可以将访问器函数简单地看作成一个调用的过程,无需输入。
以下代码可以帮助理解:
#include<iostream>
using namespace std;
class rectangular{
private:
//私有成员声明:
double width,heigth;
public:
//公有成员声明:
void fuzhi();
double getwidth() {return width;} //访问器函数
//修改器函数
void setwidth(double newwidth){
if(newwidth>0) //判断数据是否合法
width=newwidth;
cout<<width<<endl;
}
double getheigth(){return heigth;}
void setheigth(double newheigth){
if(newheigth>0)
heigth=newheigth;
cout<<heigth<<endl;
}
};
void rectangular::fuzhi(){ //类外定义函数,完成初始化
width=10.1314;
heigth=10.13580;
}
rectangular x;
int main()
{
double w,h,j,k;
cin>>w>>h;
x.fuzhi();
j=x.getwidth();
cout<<"j"<<" "<<j<<endl;
x.setwidth(w);
x.getheigth();
k=x.getheigth();
cout<<"k"<<" "<<k<<endl;
x.setheigth(h);
return 0;
}
可以尝试运行下,这样会加强对该知识点的理解。
2.6 友元
非成员函数想要访问一个类中的私有数据,可以在类中将这个函数声明为friend(友元)。
友元必须在被访问的类中声明。一个类的友元可以是全局函数,另一个类的成员函数或另一个类。
例如:类A是类B的友元隐含着A的所以成员函数都是B的友元。
程序:
友元 //------------------------- --------------------------------------
class X; //向前声明(先打个招呼),声明X是一个类,其完整定义在其他地方。
class Y{
public: void f(X*); // 对这个函数声明而言, 只需要知道X是一个类型就行了。
// X类型的指针做参数。
};
// X的完整声明和定义:
class X{
int i;
public:
void initialize(); // 类内声明,类外补充定义。
friend void g(X*,int); // 全局函数友元。
friend void Y::f(X*); // 其他类的成员函数友元。
friend class Z; // 友元类: Z的所以成员函数都是X的友元
friend void h();
};
void X::initialize() {
i = 0; // 正确,X的成员函数可以自由访问自家的私有成员。
}
void g(X* x,int i) {
x->i =i; // 正确,g()是X的友元函数,可以访问X的私有成员。
}
void Y::f(X* x) {
x ->i =47; // 正确,Y::f()是X的友元函数。
}
class Z{
int j;
public:
void initialize();
void g(X* x);
};
void Z::initialize() {
j = 99; }
void Z::g(X* x) {
x ->i +=j;
}
void h() {
X x;
X.i=100; // 正确,h()是X的友元,可以访问X对象的私有成员。
}
int main()
{
X x;
Z z;
z.g(&x);
x.i = 100; //错误, main()没有特权,不能访问X的私有数据。
}
//-------------------------------
friend的关系是不可传递的:如果A是类B的友元,而类B是类C的友元,A不会自动成为C的友元。
三、构造函数和析构函数
分别负责对象的初始化和销毁
3.1 构造函数 负责对象的初始化,能够在创建对象时被自动调用。
特点:
- 构造函数的名字和类名字相同。
- 它没有返回类型(注意: 不是void类型 )
- 构造函数的参数通常为数据成员提供初始值。
3.2 构造函数初始化列表
构造函数负责对象的初始化,但是也不是万能的,例如:如果有一成员是引用类型,就不能用赋值的方式提供初值,因为引用需要在定义时初始化。
对于const数据成员和类类型的数据成员也存在类似问题。怎么办?
针对以上情况,出现一个新知识:“构造函数初始化列表”
格式: 构造函数( 参数表 ) : 初始化列表 { 函数体 }
注意了: 在初始化列表中,每个成员只能出现一次。成员初始化的顺序与他们在类定义中定义出现的顺序一致。
3.3 委托构造函数
委托构造函数使用所属的其他构造函数执行自己的初始化过程,或者说它把自己的一些或全部职责委托给了其它构造函数。
语法形式: ClassNane (参数表) :ClassName (参数表) { 函数体 }
委托构造函数的执行流程: 当一个构造函数委托另一个构造函数时,受委托的构造函数的初始化列表和函数体依次执行,然后将控制权交还给委托者的函数体。
析构函数 作用:负责在对象生存期结束时返回相关资源和自动释放资源。当对象离开作用域时,或者用delete释放在堆上构建的对象时,析构函数都会被自动调用。
析构函数名字是类名字前加波浪线“ ~ ”。
1.析构函数没有返回类型,也没有任何参数。
2.析构重载函数。
3.只能为一个类定义唯一一个析构函数
四.const成员
4.1 const数据成员
const可以限定类中的数据成员,const数据成员一般用来描述对象中的常量,如一个学生的生日。
注意:
1. 在数据成员声明前加const关键词就将其限定为常量。
2. const数据成员在构造函数的初始化列表中初始化。
3.创建对象时初始化其中的const数据成员,之后const成员的值在对象的整个生存期中都不会改变。
4.2 const成员函数
一个类的对象可以由const限定为常量。而程序中任何试图修改const对象的操作都会引起编译错误。
声明const成员函数的语法形式为:
返回类型 成员函数名 (参数表) const;
定义const成员函数的语法形式为:
返回类型 成员函数名 (参数表)const { 函数体 }
关于const成员函数的定义和调用的几种情况,总结如下:
1.如果一个成员函数是非 const的,则只有非const对象可以调用它;const对象不能非const的成员函数。
2.如果一个成员函数是const的,则const对象可以调用它:非const对象也可以调用它,不会改变对象中的成员。
3.允许为一个成员函数定义const和非const两个版本,这两个版本是重载函数。对const对象,会选择调用const版本的成员函数;对非const对象,则调用非const成员函数:
五.static成员
适应情况:有时一个类的所以对象都需要访问某个共享的数据。
5.1 static数据成员 (属于整个类,不专属于某一成员(对象)。)
在类的数据成员声明前加关键词static,就可是数据成员成为静态的。
且static数据成员不属于某个特定对象的,因而不能再构造函数中初始化。
在类的成员函数中可以直接访问静态数据成员。
5.2 static成员函数
静态成员函数的声明是在类定义中的函数声明加static关键词,在类外定义静态成员函数不需要加关键词static。
易错点:
1. 静态成员函数没有this指针,在静态成员函数中显化或隐化地引用this指针都会引起编译错误。
2. 静态成员函数中不能访问非静态数据成员,也不能调用非静态成员函数。
3.静态成员函数也不能声明为const或volatile,因为cv限定词是限定this指针的。