每个类定义了唯一的类型,两个类即使成员相同,类型也不同。
可将类名作为类型名使用。
Sales_data item1;
class Sales_data item2;
//两种等价的声明对象方式,第二种继承于C。
类的声明和定义也可以分开。只是声明而未定义的类称为前向声明。以 Screen 类为例,在 Screen 声明而未定义之前的类型为不完全类型。
这种类型只能在非常有限的场景下使用:使用指针或引用,定义以该类型为参数或返回值的函数。
在创建某个类对象之前,类必须被定义过。
Sales_data() = default;
例如:
Sales_data(const std::string &s) : booNo(s){ }
Sales_data(const std::string &s , unsigned n , double p) : bookNo(s) , units_sold(n), revenue(p*n) { }
在冒号和花括号之间的部分称为构造函数初始值列表,它负责为创建对象的一个或几个数据成员赋初值。
如果成员是 const,引用,或者属于某种未提供默认构造函数的类类型,我们必须通过构造函数初始值列表为这些函数提供初始值。
class ConstRef {
public:
ConstRef(int ii);
private:
int i;
const int ci;
int &ri;
}
//错误:ci和ri必须被初始化
ConstRef::ConstRef(int ii) {
i = ii; //正确
ci = ii; //错误,不可以给const赋值
ri = i; //错误,ri未被初始化
}
//正确写法
ConstRef::ConstRef(int ii): i(ii), ci(ii), ri(ii){ }
C++11 新标准扩展了构造函数初始值的功能,可以定义委托构造函数。
class Sales_data{
public:
//非委托构造函数
Sales_data(std::string s, unsigned cnt , double price): bookNo(s) , units_sold(cnt), revenue(cnt*price){ }
//委托构造函数
Sales_data(): Sales_data("",0,0){}
Sales_data(std::string s): Sales_data(s,0,0){}
Sales_data(std::istream &is):Sales_data(){
//两层委任,当三个参数的非委托构造函数执行完之后,执行read();
read(is,*this);
}
}
如果构造函数只接受一个实参,它实际上定义了转化为此类型的隐式转换机制。这种构造函数称为转换构造函数。
string null_book = "9-999-99999-9";
item.combine(null_book);
//combine接受的是一个Sales_data的const的引用,此时传入字符串将调用一个单参数的构造函数生成一个临时变量。
隐式类型转换只能存在一步。
item.combine("9-999-99999-9");
//错误,存在两步隐式类型转换,首先将字符串字面值转换成string,再将string转换为Sales_data.
抑制隐式类型转换:
在需要使用隐式类型转换的上下文中,可以将构造函数声明为 explicit 加以阻止。该关键字只对含一个实参的构造函数有效。
class Sales_data{
Sales_data() = default;
explicit Sales_data(const std::string &s): bookNo(s){ }
}
explicit 构造函数只能用于直接初始化。当需要类型转换时,只可使用显式类型转换。
item.combine(Sales_data(null_book));
使用 struct 和 class 唯一的区别就是默认访问权限。使用 struct 时,在第一个访问说明符之前的所有成员是 public 的,反之使用 class 时是 private。
class Sales_data{
friend Sales_data add(const Sales_data& , const Sales_data);
friend std::istream &read (std::istream&, Sales_data& );
public:
...
private:
...
}
友元的声明仅仅改变了访问权限,并非一个通常意义上的函数声明。因此,如果希望某个类的用户调用某个友元函数,必须在友元声明以外再对函数进行一次声明。
类之间的友元关系
若一个类想要访问另一个类的成员函数,必须将其声明为友元。
class Screen{
//Windows_mgr的成员可以访问screen类的私有成员。
friend class Windows_mgr;
//Screen剩余部分
}
令成员函数作为友元
当把成员函数定义为友元时,必须同时注明他为哪个类。
class Screen{
friend void Windows_mgr::clear(ScresnIndex);
//Screen剩余部分
}
友元声明和作用域
struct X{
friend void f(){ /*可以定义在类内部*/ }
X(){ f(); } //错误,f()未声明
void g();
void h();
}
void X::g(){ return f(); }//错误,f()未声明
void f();
void X::h(){ return f(); }//正确,现在f()为已声明
名字查找过程(寻找与其所用名字最匹配的声明)
定义在类内部的成员函数,在类全部可见之后再编译其函数体。
typedef double Money;
string bal;
class Account{
public:
Money balance(){ return bal; }
private:
Money bal;
}
对于 balance 函数,首先检查 Money 的声明,在 balance 函数声明之前未找到,转向外层作用域。
由于整个类可见之后才解析函数体,故 balance 返回的 bal 为 Money 类型。
在类中,不可重新定义外层作用域定义的类型(使用 typedef)。
成员函数使用过程的名字解析
聚合类使得用户可以直接访问其成员,且具有特殊的初始化语法格式。
当一个类满足以下条件时,我们称其是聚合的。
struct Data{
int ival;
string s;
}
可以使用一个花括号括起成员初始值列表,并用其初始化聚合类数据成员。初始值顺序必须与声明一致。
Data val1 = {0, "Anna"};
使用 static 关键字声明静态成员,使用作用域运算符访问静态成员。
也可以使用类的对象,引用,或指针访问。
成员函数不通过作用域运算符也可访问。
class Account{
public:
void calculate(){ amount += amount * interestRate ; }
static double rate();
static void rate(double);
private:
std::string owner;
double amount;
static double interestRate;
static double initRate();
}
double r = Accout::rate();
Account ac1;
Account *ac2 = &ac1;
r = ac1.rate();
r = ac2->rate();
可以在类的内部和外部定义,但 static 关键字只可用于内部声明。