成员函数
-
成员函数的声明必须在类内部,成员函数的定义既可以在类内部也可以在类外部;
-
每个成员函数都绑定了隐式形参
this
,当调用成员函数时,用调用该函数的对象地址来初始化this
;// 伪代码说明 Sales_data item; item.isbn(); // 调用 Sales_data::isbn(&item); // 初始化this
-
在成员函数内部,可以对类成员进行直接访问(无须通过成员访问运算符),其实质是对
this
的隐式引用;return bookNo; // 隐式使用了this指向的成员 return this->bookNo;
-
默认情况下,
this
是一个指向类类型的常量指针;// 伪代码 std::string Sales_data::isbn(Sales_data *const this);
-
常量成员函数: 紧跟在参数列表后面的
const
表示this
是一个指向常量对象的常量指针, 这样使用const
的成员函数称为常量成员函数;因为
this
是隐式形参,不会出现在参数列表中,所以const
放在参数列表之后:std::string Sales_data::isbn() const; // this是一个指向常量对象的常量指针 // 伪代码 std::string Sales_data::isbn(const Sales_data *const);
-
成员函数体中可以随意使用其他成员而无须在意成员出现的顺序,因为编译器首先编译成员的声明,其次才是成员函数体。(也就是先声明后定义,编译顺序也是如此)。
#include <istream>
#include <ostream>
#include <string>
struct Sales_data {
// 成员函数
std::string isbn() const { return bookNo; } // 类内声明、类内定义
Sales_data &combine(const Sales_data &);
double avg_price() const;
// 数据成员
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
// 声明非成员接口函数
Sales_data add(const Sales_data &, const Sales_data &);
std::ostream &print(std::ostream &, const Sales_data &);
std::istream &read(std::istream &, Sales_data &);
// 类外定义成员函数
double Sales_data::avg_price() const {
if (units_sold) {
return revenue / units_sold;
}
return 0;
}
Sales_data &Sales_data::combine(const Sales_data &rhs) {
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this; // 返回调用此函数的对象
}
// 定义非成员函数
std::istream &read(std::istream &is, Sales_data &item) {
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
std::ostream &print(std::ostream &os, const Sales_data &item) {
os << item.isbn() << " " << item.units_sold << " " << item.revenue << " "
<< item.avg_price() << std::endl;
return os;
}
构造函数
- 构造函数的名字和类名相同;
- 构造函数没有返回类型;
- 构造函数不能被声明成const的;
- 构造函数可以有多个,类似于函数重载;
- 类没有任何构造函数时,编译器会自动生成默认构造函数;
- 当类具有自定义构造函数时,编译器不再生成默认构造函数;
- 当类具有自定义构造函数时,默认构造函数需要通过
=default
自行指定;=default
在类内部,默认构造函数是内联的;=default
在类外部,默认构造函数不是内联的;
- 构造函数初始值列表:为新创建对象的数据成员赋初值;
- 如果类成员是
const
的、引用、或者未提供默认构造函数的类类型,则必须通过构造函数初始值列表为这些成员提供初值; - 类成员的初始化顺序与出现顺序一致,构造函数初始值列表中的初始值位置不会影响实际的初始化顺序;
- 委托构造函数:使用类的其他构造函数执行自己的初始化;
- 显式构造函数:见隐式类类型转换。
#include <istream>
#include <string>
struct Sales_data {
// 数据成员
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
// 构造函数
Sales_data() = default; // 默认构造函数-内联
Sales_data(const std::string &s) : bookNo(s) {} // 构造函数-初始值列表
Sales_data(const std::string &s, unsigned n, double p)
: bookNo(s), units_sold(n), revenue(n * p) {} // 构造函数-初始值列表
Sales_data(std::istream &); // 构造函数-类内声明,类外定义
Sales_data(std::string s) : Sales_data(s, 0, 0) {} // 委托构造函数
// 成员函数
std::string isbn() const { return bookNo; }
Sales_data &combine(const Sales_data &);
double avg_price() const;
};
// 声明非成员接口函数
Sales_data add(const Sales_data &, const Sales_data &);
std::ostream &print(std::ostream &, const Sales_data &);
std::istream &read(std::istream &, Sales_data &);
// 类外定义构造函数
Sales_data::Sales_data(std::istream &is) { read(is, *this); }
// 类外定义成员函数
double Sales_data::avg_price() const {
if (units_sold) {
return revenue / units_sold;
}
return 0;
}
Sales_data &Sales_data::combine(const Sales_data &rhs) {
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this; // 返回调用此函数的对象
}
// 定义非成员函数
std::istream &read(std::istream &is, Sales_data &item) {
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
std::ostream &print(std::ostream &os, const Sales_data &item) {
os << item.isbn() << " " << item.units_sold << " " << item.revenue << " "
<< item.avg_price() << std::endl;
return os;
}
访问控制
-
类关键字唯一区别:
class
: 默认访问权限是private
;struct
:默认访问权限是public
; -
类可以让其他类或函数成为友元
friend
,以访问它的非公有成员;- 友元只能在类内部声明;
- 友元不是类成员,不受所在区域访问控制级别的约束;
- 友元仅仅指定了访问权限,不属于函数声明;
- 如果要调用友元函数,则必须在友元声明之外再专门对函数进行声明;
- 即使友元在类内部进行了定义,也必须在类外部提供相应的声明以使函数可见;
- 友元函数是隐式内联的;
-
类型成员:类可以定义某种类型在类中的别名,该别名必须先定义后使用;
-
可变数据成员:
mutable
关键字用于声明一个可变数据成员,即使在const
对象内部该成员也是可变的; -
成员函数可以内联、可以重载;
#include <cstddef>
#include <ostream>
#include <string>
class Screen {
public:
// 类型成员—先声明后使用
typedef std::string::size_type pos;
// 构造函数
Screen() = default; // 默认构造
Screen(pos ht, pos wd, char c)
: height(ht), width(wd), contents(ht * wd, c){}; // 重载构造
// 成员函数
char get() const { return contents[cursor]; } // 隐式内联--定义在内部
inline char get(pos ht, pos wd) const; // 显式内联--类内, 成员函数重载
Screen &move(pos r, pos c); // 返回*this,类外显式内联
Screen &set(char); // 返回*this,类外显式内联
Screen &set(pos, pos, char); // 返回*this,类外显式内联
Screen &display(std::ostream &os) {
do_display(os);
return *this;
} // 非常量版本
const Screen &display(std::ostream &os) const {
do_display(os);
return *this;
} // 常量版本
void incr_access() const; // const对象内也能修改access_ctr;
private:
pos cursor = 0;
pos height = 0, width = 0;
std::string contents;
mutable std::size_t access_ctr; // 可变数据成员, 记录成员函数被调用的次数
// 显示screen的内容
void do_display(std::ostream &os) const { os << contents; }
};
// 成员函数-类外显式内联
inline Screen &Screen::move(pos r, pos c) {
pos row = r * width;
cursor = row + c;
return *this;
}
// 成员函数-重载
char Screen::get(pos r, pos c) const {
pos row = r * width;
return contents[row + c];
}
// 修改可变数据成员
void Screen::incr_access() const { ++access_ctr; }
// 返回*this,类外显式内联
inline Screen &Screen::set(char c) {
contents[cursor] = c;
return *this;
}
inline Screen &Screen::set(pos row, pos col, char ch) {
contents[row * width + col] = ch;
return *this;
}
隐式类类型转换
-
转换构造函数: 如果构造函数只接受一个实参,则它实际上定义了转换为此类类型的隐式转换机制;
-
针对转换构造函数,编译器只会自动执行一次类型转换;
#include <string> class Sales_data { public: // 数据成员 std::string bookNo; // 构造函数 Sales_data() = default; // 默认构造函数-内联 Sales_data(const std::string &s) : bookNo(s) {} // 构造函数-初始值列表:转换构造函数 // 成员函数 Sales_data &combine(const Sales_data &); };
Sales_data item("No202200001"); string bookNo = "No202200002"; item.combine(bookNo); // ok, 编译器使用string对象(bookNo)隐式创建了一个Sales_data对象 item.combine("No202200002"); // not ok, 字符串字面值隐式转换为string类型,然后停止自动类型转换(编译器只会自动执行一次类型转换,不会再将string("No202200002")转换为Sales_data) item.combine(string("No202200002")); // ok, 将string("No202200002")隐式转换为Sales_data item.combine(Sales_data("No202200002")); // ok, 无隐式转换
-
显式构造函数:通过
explicit
关键字抑制构造函数定义的隐式转换; -
关键字
explicit
只对一个实参的构造函数有效(即只对转换构造函数有效); -
explicit
构造函数不能用于拷贝初始化;#include <string> class Sales_data { public: // 数据成员 std::string bookNo; // 构造函数 Sales_data() = default; // 默认构造函数-内联 explicit Sales_data(const std::string &s) : bookNo(s) {} // 构造函数-初始值列表:显式构造函数 // 成员函数 Sales_data &combine(const Sales_data &); };
string bookNo = "No202200001"; Sales_data item(bookNo); // ok,直接初始化 Sales_data item2 = bookNo; // not ok, `explicit`构造函数不能用于拷贝初始化 string bookNo2 = "No202200002"; item.combine(bookNo2); // not ok item.combine("No202200002"); // not ok item.combine(string("No202200002")); // not ok item.combine(Sales_data("No202200002")); // ok, 字符串字面值可以初始化常量引用 item.combine(Sales_data(string("No202200002"))); // ok
静态成员
- 静态成员只属于类本身,与类的任何对象均无关,即:
- 静态成员存在于任何对象之外,对象中不包含任何与静态数据成员有关的数据;
- 静态成员函数也不与任何对象绑定,不包含
this
指针; - 仍然可以使用类的对象、引用、或者指针来访问静态成员;
static
关键字只出现在类内部的声明语句中;- 除了
constexpr
类型的静态成员外,静态成员不能在类内初始化; - 即使一个常量静态成员在类内部初始化了,通常也应该在类外部定义一下该成员;
#include <string>
class Account {
public:
Account() = default;
Account(std::string owner = "", double amount = 0.0){};
void calculate() { amount += amount * interest_rate; }
static double rate() { return interest_rate; } // 公有静态成员函数
static void rate(double); // 公有静态成员函数
private:
std::string owner;
double amount;
static constexpr int period = 30; // 静态常量表达式成员,可以类内初始化
static double interest_rate; // 私有静态数据成员
static double init_rate(); // 私有静态成员函数
};
// 即使一个常量静态成员在类内部初始化了,通常也应该在类外部定义一下该成员
constexpr int Account::period;
// 静态成员不能在类内部初始化
double Account::interest_rate = 0.05;
// 定义静态成员函数
double Account::init_rate() { return 0.05; }
void Account::rate(double new_rate) { interest_rate = new_rate; }
Account ac1;
Account *ac2 = &ac1;
double r;
r = Account::rate(); // 使用类本身访问静态成员
r = ac1.rate(); // 使用类对象访问静态成员
r = ac2->rate(); // 使用对象指针访问静态成员
- 静态成员可以是不完全类型;
- 静态成员可以作为默认实参;
class Bar {
public:
//...
Bar& run(char=c); // 静态成员可以作为默认实参
private:
static Bar mem1; //ok, 静态成员可以是不完全类型
Bar *mem2; //ok, 指针成员可以是不完全类型
Bar mem3; //not ok, 数据成员必须是完全类型
static const char c;
}
参考书籍:《C++ Primer》第5版