C++Primer学习笔记07-2.类的作用域
### 类的作用域一个类就是一个作用域。类内定义的变量,类中的方法都可以访问到,因为它们是同一个作用域。
```cpp
#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;
}
```
#### 变量查找的顺序
如果不同作用域范围内都定义了名字相同的变量,则是从最小的作用域开始找,再一级一级向上找。
如果是类型名的话,则不能在不同作用域内定义相同的名字,会报错。
```cpp
typedef double Money;
class Account{
public:
// 使用外层作用域的 Money
Money balance(){ return bal; }
private:
typedef double Money; // 错误,不能重复定义 Money;
Money bal;
}
```
### 构造函数再探
与 Java 类似,会进行默认的初始化。
不同点:如果成员是 const、引用,或者属于某种未提供默认构造函数的类类型,我们必须通过构造函数初始值列表为这些成员提供初值。
#### 构造函数初始值列表
变量自身先会进行一个默认的初始化,然后构造方法在对默认初始化后的变量进行赋值。如果成员是 const 、引用或者属于某种未提供默认构造函数的类类型,必须通过构造函数初始列表提供初始值。
下面的赋值方式会出错,为什么会出错呢?就像前面说的,变量自身先会进行一个默认的初始化,然后构造方法在对默认初始化后的变量进行赋值。显然,构造函数赋值要晚于默认初始化,即构造函数赋值的变量要是可修改的(非 const 的),而 const 修饰的 ci 是不可被修改的,因此下面这种方式不行。
```cpp
#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 没有被初始化
}
```
换成以下方式即可
```cpp
ConstRef::ConstRef(int ii) : i(ii), ci(ii), ri(i) {}
```
<span style="color:red">注意,参数初始化顺序与初始化表列出的变量的顺序无关,它只与成员变量在类中声明的顺序有关。</span>
如,下面的初始值是错误的
```cpp
struct X{
X(int i, int j): base(i), rem(base % j){}
int rem, base;
};
```
#### 委托构造函数
把它自己的一些(或者全部)职责委托给了其他构造函数。简单说就是调用其他构造函数来初始化。
```cpp
class A{
public:
A(int i,int j):ii(i),jj(j);
A():A(0,0){};
private:
int i,j;
};
```
#### 默认构造函数的注意事项
```cpp
A a(); // 声明了一个函数而非对象
A aa; // 正确,定义了一个对象
```
练习
```cpp
vector<Clazz> vec(10); // 调用的有参构造,定义的是 vector 对象!
```
#### 隐式类型转换
如果构造函数只接受一个对象 A 的实参,那么它会隐式定义一种转换为此类类型的转换机制。通过调用对象 A 的构造方法来创建一个临时的 A 对象传递过去。
下面的代码展示了该隐式类型转换。
```cpp
#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 关键字禁止这种隐式转换
```cpp
#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 构造函数
```cpp
string s = "AAA";
BOOK A(s); // 正确:直接初始化
BOOK b = s; // 错误,不能将 explicit 构造函数用于拷贝形式的初始化过程。
```
不使用 explicit 上面的代码就可以进行
```cpp
#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 关键字后就不会在进行隐式类型转换了,但是可以显示的进行转换。
```cpp
#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 限定符来限定,例如
```cpp
#include <iostream>
using namespace std;
int main(int args, char* argv[]){
const int a = args;
return 0;
}
```
从上面的例子我们可以发现,虽然 a 是一个 const,但是使用一个变量初始化的。
在 C++ 中还有另外一种声明方式,叫做常量表达式(constexpr),那就需要赋值的表达式为编译期间的常量,例如
```cpp
#include <iostream>
using namespace std;
int main(int args, char* argv[]){
constexpr int b = args;
return 0;
}
```
此时编译就会报错,如下
```shell
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 构造函数。
- 类必须使用析构函数的默认定义,该成员负责销毁类的对象。
例如如下类:
```cpp
#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。
```cpp
#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++标准库
- IO 库
- 顺序容器
- 泛型算法
- 关联容器
- 动态内存
页:
[1]