登录  | 立即注册

游客您好!登录后享受更多精彩

查看: 94|回复: 0

C++Primer学习笔记07-2.类的作用域

[复制链接]

44

主题

-24

回帖

30

积分

新手上路

积分
30
发表于 2025-1-4 22:44:48 | 显示全部楼层 |阅读模式

类的作用域

一个类就是一个作用域。类内定义的变量,类中的方法都可以访问到,因为它们是同一个作用域。

#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++标准库

  • IO 库
  • 顺序容器
  • 泛型算法
  • 关联容器
  • 动态内存
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|断点社区 |网站地图

GMT+8, 2025-1-18 18:47 , Processed in 0.068231 second(s), 27 queries .

Powered by XiunoBBS

Copyright © 2001-2025, 断点社区.

快速回复 返回顶部 返回列表