类的作用域
一个类就是一个作用域。类内定义的变量,类中的方法都可以访问到,因为它们是同一个作用域。
#include <iostream>
using std::cout;
using std::cin;
class Domain {
public:
    typedef int pos;
    Domain() = default;
    Domain(pos px,pos py): x(px),y(py){};
    // 可以访问到类内的成员
    int getX() { return x; }
    pos getY();
private:
    int x, y;
};
// 通过 类::方法,来说明这个方法是在 类的作用域内
// 可以访问类内的成员,由于 pos 也是类内定义的
// 所以返回值也要用 ::
Domain::pos Domain::getY() {
    return y;
}
int main() {
    Domain domain(1,2);
    int x = domain.getX();
    Domain::pos y = domain.getY();
    cout << x << ":" << y;
}
变量查找的顺序
如果不同作用域范围内都定义了名字相同的变量,则是从最小的作用域开始找,再一级一级向上找。
如果是类型名的话,则不能在不同作用域内定义相同的名字,会报错。
typedef double Money;
class Account{
public:
    // 使用外层作用域的 Money
    Money balance(){ return bal; }
private:
    typedef double Money; // 错误,不能重复定义 Money;
    Money bal;
}
构造函数再探
与 Java 类似,会进行默认的初始化。
不同点:如果成员是 const、引用,或者属于某种未提供默认构造函数的类类型,我们必须通过构造函数初始值列表为这些成员提供初值。
构造函数初始值列表
变量自身先会进行一个默认的初始化,然后构造方法在对默认初始化后的变量进行赋值。如果成员是 const 、引用或者属于某种未提供默认构造函数的类类型,必须通过构造函数初始列表提供初始值。
下面的赋值方式会出错,为什么会出错呢?就像前面说的,变量自身先会进行一个默认的初始化,然后构造方法在对默认初始化后的变量进行赋值。显然,构造函数赋值要晚于默认初始化,即构造函数赋值的变量要是可修改的(非 const 的),而 const 修饰的 ci 是不可被修改的,因此下面这种方式不行。
#include <iostream>
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(i) {}
<span style="color:red">注意,参数初始化顺序与初始化表列出的变量的顺序无关,它只与成员变量在类中声明的顺序有关。</span>
如,下面的初始值是错误的
struct X{
    X(int i, int j): base(i), rem(base % j){}
    int rem, base;
};
委托构造函数
把它自己的一些(或者全部)职责委托给了其他构造函数。简单说就是调用其他构造函数来初始化。
class A{
public:
    A(int i,int j):ii(i),jj(j);
    A():A(0,0){};
private:
    int i,j;
};
默认构造函数的注意事项
A a(); // 声明了一个函数而非对象
A aa; // 正确,定义了一个对象
练习
vector<Clazz> vec(10); // 调用的有参构造,定义的是 vector 对象!
隐式类型转换
如果构造函数只接受一个对象 A 的实参,那么它会隐式定义一种转换为此类类型的转换机制。通过调用对象 A 的构造方法来创建一个临时的 A 对象传递过去。
下面的代码展示了该隐式类型转换。
#include "stdafx.h"
#include <string>
#include <iostream>
using namespace std ;
class BOOK{
    private:
        string _bookISBN ;  //书的ISBN号
        float _price ;    //书的价格
    public:
        //定义了一个成员函数,这个函数即是那个“期待一个实参为类类型的函数”
        //这个函数用于比较两本书的ISBN号是否相同
        bool isSameISBN(const BOOK & other ){
            return other._bookISBN==_bookISBN;
        }
        //类的构造函数,即那个“能够用一个参数进行调用的构造函数”(虽然它有两个形参,但其中一个有默认实参,只用一个参数也能进行调用)
        BOOK(string ISBN,float price=0.0f):_bookISBN(ISBN),_price(price){}
};
int main(){
    BOOK A("A-A-A");
    BOOK B("B-B-B");
    cout<<A.isSameISBN(B)<<endl;   //正经地进行比较,无需发生转换
    cout<<A.isSameISBN(string("A-A-A"))<<endl; //此处即发生一个隐式转换:string类型-->BOOK类型,借助BOOK的构造函数进行转换,以满足isSameISBN函数的参数期待。
    cout<<A.isSameISBN(BOOK("A-A-A"))<<endl;    //显式创建临时对象,也即是编译器干的事情。
    system("pause");
}
可以使用 explicit 关键字禁止这种隐式转换
#include <string>
#include <iostream>
using namespace std ;
class BOOK{
private:
    string _bookISBN ;  //书的ISBN号
    float _price ;    //书的价格
public:
    //定义了一个成员函数,这个函数即是那个“期待一个实参为类类型的函数”
    //这个函数用于比较两本书的ISBN号是否相同
    bool isSameISBN(const BOOK & other ){
        return other._bookISBN==_bookISBN;
    }
    //类的构造函数,即那个“能够用一个参数进行调用的构造函数”(虽然它有两个形参,但其中一个有默认实参,只用一个参数也能进行调用)
    explicit BOOK(string ISBN,float price=0.0f):_bookISBN(ISBN),_price(price){}
};
int main(){
    BOOK A("A-A-A");
    BOOK B("B-B-B");
    cout<<A.isSameISBN(B)<<endl;   //正经地进行比较,无需发生转换
    /*
    No viable conversion from 'std::string' (aka 'basic_string<char>') 
    to 'const BOOK' Book.cpp:17:14: note: explicit constructor is not 
    a candidate Book.cpp:12:34: note: passing argument to parameter 'other' here
    */
    cout<<A.isSameISBN(string("A-A-A"))<<endl; 
    cout<<A.isSameISBN(BOOK("A-A-A"))<<endl;    //显式创建临时对象,也即是编译器干的事情。
}
关键字 explicit 只对一个实参的构造函数有效。需要多个实参的构造函数不能用于执行隐式转换,所以无须将这些构造函数指定为 explicit 的。
<b>explicit 构造函数只能用于直接初始化</b>
发生隐式转换的一种情况是当我们执行拷贝形式的初始化时(使用=)。此时,我们只能使用直接初始化而不能使用 explicit 构造函数
string s = "AAA";
BOOK A(s); // 正确:直接初始化
BOOK b = s; // 错误,不能将 explicit 构造函数用于拷贝形式的初始化过程。
不使用 explicit 上面的代码就可以进行
#include <string>
#include <iostream>
using namespace std;
class BOOK {
private:
    string _bookISBN;  //书的ISBN号
    float _price;    //书的价格
public:
    void test(){cout<<"hello"<<endl;}
    BOOK(string ISBN, float price = 0.0f) : _bookISBN(ISBN), _price(price) {}
};
int main() {
    BOOK B("B-B-B");
    string s = "!";
    BOOK b = s;
    b.test(); // hello
}
<b>显示的进行类型转换</b>
使用了 explicit 关键字后就不会在进行隐式类型转换了,但是可以显示的进行转换。
#include <string>
#include <iostream>
using namespace std;
class BOOK {
private:
    string _bookISBN;  //书的ISBN号
public:
    bool isSameISBN(const BOOK &other) {
        return other._bookISBN == _bookISBN;
    }
    void test() { cout << "jello" << endl; }
    //类的构造函数,即那个“能够用一个参数进行调用的构造函数”(虽然它有两个形参,但其中一个有默认实参,只用一个参数也能进行调用)
    explicit BOOK(string ISBN) : _bookISBN(ISBN) {}
};
int main() {
    BOOK b("hello");
    string str = "hello";
    cout << b.isSameISBN(static_cast<BOOK>(str));
}
字面值常量类
在 C++ 中,定义变量的时候可以指定常量属性,说明这个变量成为常量,无法直接改变;这个使用 const 限定符来限定,例如
#include <iostream>
using namespace std;
int main(int args, char* argv[]){
        const int a = args;
        return 0;
}
从上面的例子我们可以发现,虽然 a 是一个 const,但是使用一个变量初始化的。
在 C++ 中还有另外一种声明方式,叫做常量表达式(constexpr),那就需要赋值的表达式为编译期间的常量,例如
#include <iostream>
using namespace std;
int main(int args, char* argv[]){
        constexpr int b = args;
        return 0;
}
此时编译就会报错,如下
constexpr.cpp: In function ‘int main(int, char**)’:
constexpr.cpp:6:20: error: ‘args’ is not a constant expression
  constexpr int b = args;
                    ^~~~
对于普通变量我们比较好理解,但是 C++ 的类,也能声明成为字面值的常量类。
<b>字面值的常量类有两种定义</b>
- 数据成员都是字面值类型(算术类型,引用和指针,以及字面值常量类)的聚合类是字面值常量类。
- 或者满足如下的定义:
- 数据成员都必须是字面值类型(算术类型,引用和指针,以及字面值常量类)。
- 类必须至少含有一个 constexpr 构造函数。
- 如果一个数据成员含有类内初始值,则内置类型的初始值必须是一条常量表达式。或者如果成员属性某种类类型,则初始值必须使用成员自己的 constexpr 构造函数。
- 类必须使用析构函数的默认定义,该成员负责销毁类的对象。
 
例如如下类:
#include <iostream>
using namespace std;
class CPoint{
public:
        constexpr CPoint(int xx, int yy) : x(xx), y(yy){}
        void setx(int xx){x = xx;}
        void sety(int yy){y=yy;}
        constexpr int getx() const {return x;}
        constexpr int gety() const {return y;}
private:
        int x;
        int y;
};
int main(int args, char* argv[]){
        constexpr CPoint point(100, 200);
        constexpr int data = point.getx() * point.gety();
        cout << data << endl;
        cout << point.getx() * point.gety() << endl;
        return 0;
}
类的静态成员
用法与 Java 类似,静态成员与对象无关,可以通过对象.访问也可以通过类::静态访问,区别在于
- C++ 静态方法的声明只能在类内使用 static 关键字,定义的时候就不能再有 static 关键字了。
- 静态成员函数不能声明成 const 的。
- 可以为静态成员提供 const 整数类型的类内初始值,不过要求静态成员必须是字面值常量类型的 constexpr。
#include <iostream>
#include <string>
using namespace std;
class Account {
public:
    static double rate() { return interestRate; };
    static void rate(double);
private:
    std::string owner;
    double amount;
    static double interestRate;
};
// 类外部定义 static 函数不能重复使用 static 关键字
// 成员函数内部使用 static 成员无需使用 :: 符号,
// 因为就是在同一个作用域内
void Account::rate(double n) {
    interestRate = n;
}
int main() {
    Account account;
    account.rate(10.1);
    cout << Account::rate() << endl;
}
第Ⅱ部分-C++标准库