1. 类概述
- 类的基本思想:数据抽象和封装
2. 内联函数
隐式内联:
- constexpr函数为隐式内联函数;
- 在类内定义的函数为隐式内联函数;
显式内联
- 类内显式inline声明并定义;
- 类内显式inline声明,类外定义;
- 类内无显式声明,类外追加inline定义;
3. const成员函数
class Sales_data{
// 数据成员
std::string bookNo;
// const成员函数
std::string isbn() const {return bookNo;}
};
- 非const对象既可调用非const成员函数,也可以调用const成员函数;
- 而const对象只能调用const成员函数;
- 因为非常量对象可以隐式的转换成常量对象,而常量对象不可以转换成非常量对象。
- 深层次的原因,我的另一篇博客:深入理解为什么要有const成员函数
4. this
- 成员函数的隐式形参,类似于python类中的self;
- this默认是一个指向非常量对象的常量指针,const成员函数的this形参是一个指向常量对象的常量指针;
- 返回*this的成员函数返回的是调用该函数的对象的引用:
Sales_data& Sales_data::combine(const Sales_data &data2)
{
......
return *this
}
如上:谁调用的combine函数,返回的就是哪个对象的引用。
5. 构造函数
- 构造函数不能被声明成const,当创建一个const类对象时,直到构造函数完成初始化过程,对象才取得“常量”属性,因此构造函数中可以向对象写值。
class Sales_data{
// 数据成员
std::string bookNo; // 如果是默认初始化,就初始化为空string
unsigned units_sold=0; // 提供流类内初始值,因此使用合成默认构造函数就会按照类内初始值进行初始化
double revenue=0.0; // 同上
// const成员函数
std::string isbn() const {return bookNo;}
//构造函数
Sales_data()=default; // 默认构造函数,但不是自动合成的!!是手动定义的
Sales_data(const string &s):bookNo(s){} // 构造函数初始值列表
Sales_data(const string &s, unsigned n, double p):bookNo(s), units_sold(n), revenue(p*n){} // 构造函数初始值列表
默认构造函数
- 如果不编写构造函数,编译器将自动合成默认的构造函数,即按照默认初始化或者类内初始值(c++11)来初始化数据;
构造函数初始值列表
- 初始化
Sales_data(const string &s, unsigned n, double p):bookNo(s), units_sold(n), revenue(p*n){}
- 结果等同于赋值:
Sales_data(const string &s, unsigned n, double p)
{
bookNo = s;
units_sold = n;
revenue = p*n;
}
类似于python的:
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
-
特殊情况:如果数据成员是const或者引用,则必须使用构造函数初始值列表进行初始化,不能赋值。
例如:如果一个类的数据成员如下class ConstRef{ public: ConstRef(int); private: int i; const int ci; int &ri; }
则其构造函数必须为:
ConstRef::ConstRef(int val):i(val),ci(val),ri(i){}
-
成员初始化的顺序:
- 构造函数初始值列表的顺序不会影响实际的初始化顺序,实际的初始化顺序与在定义中出现的顺序一致,如下例的实际顺序是先i后j,因此在构造函数初始值列表中使用i(j),即用j来初始化i是不对的。
class X{
int i;
int j;
public:
X(int val):j(val), i(j){} //这里是错误的
};
隐式的类类型转换
-
通过一个实参调用的构造函数定义了一条从构造函数参数类型向类类型隐式转换的规则;
Sales_data(const string &s):bookNo(s){}
-
该构造函数定义了一个从const string 类型向Sales_data类型转换的规则,即在需要使用Sales_data的地方,可以使用string代替:
string null_book="9999999"; item.combine(null_book);
-
但是,注意,只允许一步隐式转换:
item.combine("9999999");//错误,因为先转换为string,又转换为Sales_data; item.combine(string("9999999"));//正确,显式转换到string,然后隐式转换到Sales_data; item.combine(Sales_data("9999999"));//正确,隐式转换到string,然后显式转换到Sales_data;
抑制 隐式类类型转换
- 构造函数声明为explicit:注意!explicit只对含一个参数的构造函数有效。
explicit Sales_data(const string &s):bookNo(s){}
- 此时,不存在参数类型向类类型的隐式转换,因此下面的代码是错误的:
string null_book="9999999"; item.combine(null_book);
- explicit构造函数只能用于直接初始化
Sales_data item1(null_book);//直接初始化,正确
Sales_data item2=null_book;//拷贝初始化,错误
6. 访问控制
struct和class的区别
- 相同点:在访问说明符public之后是共有对象,在private之后是私有对象;
- 不同点(唯一不同点):在第一个访问说明符之前的成员,struct是public,class是private;
public, private, protected区别
访问运算符 | public | private | protected |
---|---|---|---|
类外用户 | √ | × | × |
类内成员 | √ | √ | √ |
派生类 | √ | × | √ |
友元 | √ | √ | √ |
7. 友元
友元函数
- 类的友元函数是非成员函数,但是可以访问类的private成员;
- 声明的方式:在普通的函数声明前加一个friend关键字:
friend Sales_data add(const Sales_data&, const Sales_data&);
友元类
- 类还可以将其他类定义为友元,友元类可以访问该类的所有成员:
class Screen{
friend class Window_mgr; //Window_mgr的成员可以访问Screen的所有成员
}
- 每个类负责控制自己的友元类或友元函数;
友元成员函数
- 类还可以指定其他类的某个成员函数为友元,声明时必须明确指出该成员函数属于哪个类;
class Screen{
friend void Window_mgr::clear(ScreenIndex);
}
友元重载函数
- 如果想声明一组重载函数为友元,必须分别声明,因为他们是不同的函数;
8. 类的作用域
- 定义在类外部的成员,不仅要在函数名前使用作用域运算符,而且如果返回类型也是类成员,也必须使用作用域运算符访问:
Window_mgr::ScreenIndex Window_mgr::addScreen(const Screen &s){
...
}
- 类编译的顺序:首先编译所有声明,直到整个类可见之后,再编译成员函数体。因此成员函数和数据类型的定义可以不用区分先后顺序。
9. 类的静态成员
- 目的:有时需要一些成员与类本身相关,而不是与各个对象相关;
- 通过在成员声明前加上关键字static使其与类关联:
class Account{
public:
static double rate(){return interestRate;} //类内定义静态成员函数
static void rate(double); //类内声明,类外定义静态成员函数
private:
std::string owner; //普通成员
double amount; //普通成员
static double interestRate; //静态成员
static double initRate(); //类内声明,类外定义静态成员函数
}
- 访问类的静态成员:既可以通过类的对象访问,也可以通过作用域运算符直接访问,成员函数可以直接使用静态成员;
double r1,r2;
Account ac1;
r1=ac1.rate(); //通过对象访问
r2=Account::rate(); //直接访问
- 类外定义静态成员:必须指定类名,且不需要static关键字(成员函数可以直接使用);
- 类的静态成员必须在类外定义和初始化;
void Account::rate(double newRate)
{
interestRate = newRate; //不需要static关键字
}
- 静态成员函数中不能访问非静态成员,因为静态成员属于类,在类实例化之前就已经分配空间,而非静态成员只有在实例化的时候才会初始化;但非静态成员函数可以访问静态成员;
- 类的静态成员变量必须初始化;