一,定义
类的基本思想是数据抽象和封装。数据抽象是一种把接口和实现分离的编程技术。类的接口包括用户所能够执行的操作,类的实现包括类的数据成员、负责接口实现的函数体和各种私有函数。
封装实现了类的接口和实现的分离。封装隐藏了类的实现,封装过后,用户只能访问类的接口,而不能访问类的实现。
1.类的声明:
以数据成员的方式描述数据部分,以成员函数(被称为方法)的方式描述接口函数。
class 类名称
{
public:
公有成员(外部接口)
private:
私有成员 (只允许本类中的函数访问,而类外部的任何函数都不能访问)
protected:
保护成员(与private类似,差别表现在继承与派生时)
};
2.类方法定义:
描述如何实现类成员函数。
简单地说,类声明给出了类的框架,而方法定义给出了类的细节。
3.类与结构体的区别
class数据成员默认私有
struct数据成员默认公有
4.数据成员
类中有两个关键字private和public,它们描述了程序对类的成员的访问控制。由于隐藏数据是OOP的主要目的之一,所以数据成员一般放在private部分,而接口函数则放在public部分。
类可以没有成员,也可以定义多个成员。成员可以是数据、函数或类型别名。所有的成员都必须在类的内部声明。
没有成员的类是空类,空类也占用空间。
注:类的成员默认是私有的,但是为了严谨一般还是用private关键字注明,而结构体的成员默认是公有的。
5.成员函数
(1)在类外定义一个成员函数时,需要使用域解析运算符(::)来标识函数所属的类。
这意味着,不同的类中,可以定义同样的成员函数而互不影响。
成员函数必须在类内部声明,可以在类内部定义,也可以在类外部定义。如果在类内部定义,就默认是内联函数。
在类内定义的函数默认为inline函数。
如果要在类外定义inline函数,需要在定义或者声明前加关键字inline。
二,访问控制和封装
1.将数据和操作捆绑在一起,并加上访问控制,称为封装
数据描述对象的属性,操作描述对象的行为
public,private,protected被称为访问限定符。
结构体以分号结束,类也以分号结束。
对象使用成员函数时使用成员运算符: .
对类的定义就是定义了一个具体的数据类型,要使用它我们必须将类实例化,即定义该类的对象。
2.this指针
this指针指向对象,它的值是当前被调用的成员函数所在的对象的起始地址。
一个经典的例子
假设你有一个图纸,你按照这个图纸造了许多房子,这些房子外部分别标了不同的名称以区别,但是它的内部陈设都一样;
当你进入房子时,你可以看见房子里的物品:桌子,凳子等,当你却看不到房子的全貌了,你对房子的“内部陈设”动了手脚,但此时你已经不知道你进入的是哪个房子了,动的是哪个房子的东西了
-
每个对象都拥有一个this指针,通过this指针来访问自己的地址
-
每个成员函数都有一个指针形参
,它的名字是固定的,称为this指针,this指针是隐式的
。(构造函数比较特殊,没有这个隐含this形参) - this指针是成员函数隐含指针形参,
是编译器自己处理的
,我们不能在成员函数的形参中添加this指针的参数定义,也不能在调用时显示传递对象的地址给this指针。 - 编译器会对成员函数进行处理,在对象调用成员函数时,对象地址作实参传递给成员函数的第一个形参this指针,如图
为什么需要this?因为this作用域是在类的内部,自己声明一个类的时候,还不知道实例化对象的名字,所以用this来使用对象变量的自身。在非静态成员函数中,编译器在编译的时候加上this作为隐含形参,通过this来访问各个成员(即使你没有写上this指针)。例如a.fun(1)<==等价于==>fun(&a,1)
3.友元
友元一般存在于不同类之间,在一个类中,可以用全局函数作友元函数。而在不同类中,类成员函数作友元函数
友元可以是一个函数,该函数被称为友元函数,函数既可以是全局也可以是类的成员;友元也可以是一个类,该类被称为友元类。
同类对象间无私处,异类对象间有友元
友元目的本质:
是让其它不属于本类的成员(全局函数,其它类的成员函数),成为类的成员而具备了本类成员的属性。
友元函数是可以直接访问类的私有成员的非成员函数。它是定义在类外的普通函数,它不属于任何类,但需要在类的定义中加以声明,声明时只需在友元的名称前加上关键字friend,其格式如下:
friend 类型函数名(形式参数);
例:
#include<iostream>
#include<bits/stdc++.h>
#include<string>
#include<algorithm>
using namespace std;
class Point; //由于manager中用到了Point类引用,引用是可以作前向声明的,P所以oint可以但是manager不行
class manager
{
public:
double getDistance(Point & a,Point & b);
};
class Point
{
public:
Point(double xx,double yy)
{
x=xx;
y=yy;
}
friend double manager::getDistance(Point & a,Point & b);
private:
double x,y;
};
double manager::getDistance(Point & a,Point & b)
{
double dx=a.x-b.x;
double dy=a.y-b.y;
return sqrt(dx*dx+dy*dy);
}
int main()
{
Point aa(1,2); Point bb(3,4);
manager cc;
double chen =cc.getDistance(aa,bb);
cout<<chen<<endl;
return 0;
}
输出结果:
把一个类作为另一个类的友元
声明谁的友元,就可以通过谁的对象,访问谁的数据成员
例:
class Point
{
public:
Point(double xx,double yy)
{
x=xx;
y=yy;
}
friend class manager;
private:
double x,y;
};
class manager
{
public:
double getDistance(Point & a,Point & b);
double getTriDistance(Point & a,Point &b);
};
double manager::getDistance(Point & a,Point & b)
{
double dx=a.x-b.x;
double dy=a.y-b.y;
return sqrt(dx*dx+dy*dy);
}
double manager::getTriDistance(Point & a,Point & b)
{
}
int main()
{
Point aa(1,2); Point bb(3,4);
manager cc;
double chen =cc.getDistance(aa,bb);
cout<<chen<<endl;
return 0;
}
注意:
(1)友元关系不能被继承。
(2)友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。
(3)友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的申明
三,构造函数,析构函数
1.构造函数
<1>作用:赋初值,初始化对象的数据成员,由编译器帮我们调用。
<2>特点:①函数名和类名一样。②没有返回值。③支持有参/无参。④可以重载。
<3>调用时机:在类的对象创建时刻,编译器帮我们调用构造函数。
一个类内构造函数至少写两个,一个不带参数一个带参数。
例:
class Base
{
public:
Base(int var) : m_Var(var)
{
}
private:
int m_Var;
};
以上构造函数的执行过程:
1)传参
2)给类数据成员开辟空间
3)执行冒号语法给数据成员初始化
4)执行构造函数括号里面的内容
这里需要说明的是:冒号语法后面的内容相当于int a = 10;(初始化),而构造函数括号里面则是相当于是int a; a = 10;(赋初值)
拷贝构造函数
class Base
{
public:
Base(int var) : m_Var(var)
{
}
//拷贝构造函数
Base(Base &ref) : m_Var(ref.m_Var)
{
}
private:
int m_Var;
};
拷贝构造函数的参数只能用引用
委托构造函数
和其他构造函数一样,一个委托构造函数也有一个成员初始值列表和一个函数体。在委托构造函数内,成员初始值列表只有一个唯一的入口,就是类名本身。和其他成员初始值一样,类名后面紧跟圆括号括起来的参数列表,参数列表必须跟类中另外一个函数匹配。
例:
class Date
{
public:
//非委托构造函数使用对应的实参初始化成员
Date(int year,int month,int day)
:_year(year)
, _month(month)
, _day(day)
{}
//其余构造函数全都委托给另一个构造函数
Date() :Date(1990,1,1)
{}
Date(int year) :Date(){}
private:
int _year;
int _month;
int _day;
};
2.析构函数
<1>作用:用于释放资源。
<2>特点:①和类名一样,不过得在前面加上~。②无参数,无返回值。③因为无参数,无返回值,所以不可以重载。④尽量不要自己调用析构 函数,但是在某些需要的时候再调用。
<3>调用时机:快退出函数的时候,编译器帮我们调用。
析构函数的名字是类名字前加波浪线“~”
四,const成员
1.const成员
在c++中不允许改变的数据成员,我们将它声明为const成员。
const成员的声明形式是
const+类型+变量名;
const成员必须用初始化表的形式进行初始化。
2.const成员函数
一次性不可修改
const 修饰成员函数表示传入函数内的为 const *this
const 成员函数:
(1)、不允许修改成员变量;
(2)、mutable修饰符的成员变量,对于任何情况下通过任何手段都可修改,自然此时的const成员函数是可以修改它的;
(3)、不允许访问非const函数。
class Demo
{
public:
void print() const; //常成员函数
int get() const;
void set();
private:
int m_int;
mutable int m_mut;
};
void Demo::print(/* const Demo *this */) const //相当于传入了一个const 的this指针
{
m_int = 0;//error,(1)
set(); //error (2)
get(); //ok
m_mut = 20;//ok
const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数。
五,static成员
不属于某一个对象,属于全部对象共享。
Static数据成员在类定义时就存在了,所以有不同于一般数据成员的特性
1. Static数据成员必须在类外部定义,且正好定义一次,static成员属于类,在类定义时初始化,而不是想普通数据成员在对象建立时通过构造函数初始化。所以static数据成员定义不应该放在头文件中,应该放在类非内联函数定义地方
2. 静态数据成员被类的所有对象所共享,包括该类派生类的对象。即派生类对象与基类对象共享基类的静态数据成员
3. 静态数据成员的类型可以是所属类的类型,而普通数据成员则不可以。普通数据成员的只能声明为 所属类类型的 指针或引用
4. 静态数据成员的值在const成员函数中可以被合法的改变(const函数只能禁止该对象值,却不能禁止该类的成员值)。
5. 静态数据成员可以成为成员函数的默认参数,而普通数据成员则不可以。
6. 一般数据成员只能在对象初始化时初始化,static成员在类定义时体外部初始化,而const static数据成员可以在类内部初始化
static成员函数
1. static成员函数没有this指针,所以只能访问static数据成员
2. 静态成员函数不可以同时声明为 virtual、const、volatile函数。
例
class base{
virtual static void func1();//错误
static void func2() const;//错误
static void func3() volatile;//错误
};
3. 静态成员函数的地址可用普通函数指针储存,而普通成员函数地址需要用 类成员函数指针来储存。
例:
class base{
static int func1();
int func2();
};
int (*pf1)()=&base::func1;//普通的函数指针
int (base::*pf2)()=&base::func2;//成员函数指针