c++中的方法

c++中的方法

static方法

与数据成员类似,方法有时会应用于全部对象而不是单个对象。可以编写static方法和数据成员。在方法声明前加上static即可。对于方法的定义前则不需要重复使用static关键字。

class Foo
{
public:
    static int sumFunc(int a, int b);
};

int Foo::sumFunc(int a, int b)
{
    cout << format("a + b = {}", a + b) << endl;
    return a + b;
}

需要注意的是static方法不属于某个特定的对象,因此没有this指针。当用某个特定对象调用static方法时,static方法不会访问这个对象的非static数据成员。实际上,static方法就像普通函数一样,唯一不同之处在于它可以访问类的private和protected的static成员。如果同一个类型的其他对象对于static方法可见(例如传递了对象的指针,或者引用作为参数),那么static方法也可以访问其他对象的private和protected的非static数据成员。

如果想要在类的外面调用静态方法,需要用类名+作用域解析运算符来限定方法的名称,静态方法的访问控制和普通方法一样。

const方法

const对象的值不可以改变。如果使用const对象、const对象的引用和指向const对象的指针,编译器将不允许调用对象的任何方法,除非这些方法保证不改变任何数据成员。为了保证方法不改变数据成员,可以使用const关键字标记方法本身。

class Foo
{
public:
    Foo(int a, int b) : m_proa(a), m_pria(a) {}
    static int sumFunc(int a, int b, Foo &foo);
    
    void print(Foo &foo) const;
protected:
    int m_proa;
    static int m_prob;

private:
    int m_pria;
    static int m_prib;
};
int Foo::m_prob(10);
int Foo::m_prib(11);

// const说明符是方法原型的一部分,必须放在方法的定义中!
void Foo::print(Foo &foo) const
{
    m_pria = 10; // 错误,不可以修改成员变量
    cout << "foo.pria = " << foo.m_pria << endl;
}

将方法标记为const,就是与客户代码订下了约定,承诺不会在方法内改变对象内部的值。const的工作原理是将方法内用到的数据成员都标记为const引用,因此如果试图修改数据成员,编译器会报错。

不能将static方法声明为const,编译器会报错,因为这样是多余的。静态方法没有类的事例,因此不可能改变内部的值。

非const对象可以调用const方法和非const方法。然而,const对象只能调用const方法。

在这里插入图片描述

所以应该养成良好的编程习惯,将不修改对象的所有方法声明为const,这样就可以在程序中使用const对象的引用。注意const对象也会被销毁,它们的析构函数也会被调用,因此不应该将析构函数标记为const。

mutable数据成员

有时候编写的方法在逻辑上是const方法,但是碰巧改变了对象的数据成员。这个改动对于用户可见的数据没有任何影响,但是严格来说的确做了改变,因此编译器不允许将这个方法声明为const。

例如我们想统计一个函数的执行次数,很简单,只需要加入一个计数器来计算调用次数即可。但是因为要改变这个计数器的值,所以不能将该方法设置为const。解决办法就是将计数器这个变量设置为mutable,告诉编译器const()方法中允许修改这个值。示例如下:

class Foo
{
public:
    Foo(int a, int b) : m_proa(a), m_pria(a) {}
    static int sumFunc(int a, int b);
    int sum3Func(int a, int b, int c);

    void print(Foo &foo) const;

protected:
    int m_proa;
    static int m_prob;

private:
    int m_pria;
    mutable int count{0};
    static int m_prib;
};
int Foo::m_prob(10);
int Foo::m_prib(11);

void Foo::print(Foo &foo) const
{
    ++count;
    cout << "foo.pria = " << foo.m_pria << endl;
}

方法重载

函数名称相同,参数的个数或类型不同,这样的方法就是重载,例如最简单的无参构造函数与有参构造函数,函数的参数个数不同,名字相同所以构成了重载。c++不允许仅根据方法的返回类型重载方法名称,因为在大多数情况下不能判断调用哪个方法实例。例如,如果任何地方都没有使用方法的返回值,编译器将无法判断要使用哪个方法的实例。

可以通过生成 -o文件,然后在使用nm命令查看!示例:

class Foo
{
public:
    int add(int a, int b);
    double add(double a, double b);
    int add(int a, int b, int c);
};

int Foo::add(int a, int b)
{
    return a + b;
}

double Foo::add(double a, double b)
{
    return a + b;
}

int Foo::add(int a, int b, int c)
{
    return a + b + c;
}

int main()
{
    Foo foo;
    int a{1}, b{2}, c{3};
    double d{5}, e{6};
    cout << "add = " << foo.add(a, b) << endl;
    cout << "add = " << foo.add(d, e) << endl;
    cout << "add = " << foo.add(a, b, c) << endl;
}

执行操作

g++ test.cc -c
nm test.o

输出关键结果:

0000000000000018 T _ZN3Foo3addEdd
0000000000000000 T _ZN3Foo3addEii
0000000000000040 T _ZN3Foo3addEiii

可以看到addE后面一个有两个’d’,一个有两个’i’、一个是有三个’i’。基本上可以看出重载的规则是根据参数的个数、类型有关,与返回类型无关。

基于const的重载

还要注意的是,可以根据const重载方法。也就是说,可以编写两个名称相同、参数也相同的方法,其中一个是const,另外一个不是。如果是const对象,就调用const方法;如果是非const对象,就调用非const方法。

示例:

class Foo
{
public:
    int add(int a, int b);
    int add(int a, int b) const;
};

通常情况下const版本与非const版本的实现是一样的。为避免代码重复,可以使用std::as_const()在非const方法内直接调用重载的const方法。ps:c++17引入的as_const()定义在< utility>中,功能是将左值转成const类型。

int Foo::add(int a, int b) const
{
    cout << "add(i, i)" << endl;
    return a + b;
}

int Foo::add(int a, int b)
{
    return as_const(*this).add(a, b);
}

显示删除重载

重载方法可以显示被删除,可以用这种方法禁止调用具有特定参数的数据成员函数。例如:

cout << foo.add(12.0, 34.0) << endl;
cout << foo.add(12, 34) << endl;

第二行,编译器会调用add(double, double)的重载方法,由于某些原因,你不想以整型的方式调用add,那么可以显示删除add的整型重载版本。

int add(int a, int b) const = delete;

通过这一改动,以整型为参数调用,编译器就会提示报错。

引用限定方法

可以对类的非临时和临时实例调用普通类方法,假设有以下类:

class TextHolder {
public:
    TextHolder(string text) : m_text { move(text) } {}
    const string& getText() const { return m_text; };
private:
    string m_text;
};

毫无疑问,可以在TextHolder的非临时实例上调用getText()方法,例如:

TextHolder textHolder {"hello world"};
cout << textHolder.getText() << endl;

然而,getText()也可以被临时实例调用

cout << TextHolder{ "hello world" }.getText() << endl;
cout << move(textHolder).getText() << endl;

可以显示指定能够调用某个方法的实例类型,无论是临时实例还是非临时实例。这是通过向方法添加一个所谓的引用限定符来实现的。如果只应用在非临时实例上调用方法,则在方法头之后添加一个&限定符。类似地,如果只应用在临时实例上调用方法,则要添加一个&&限定符。

下面修改的textHolder类通过将返回m_text的const引用来实现&限定的getText()。另一方面,&&限定的getText()返回m_text的右值引用,以便可以将m_text移出TextHolder。例如,如果你希望从临时TextHolder实例检索文本,则这可能更有效。

class TextHolder
{
public:
    TextHolder(string text) : m_text{move(text)} {}
    const string &getText() const &
    {
        cout << "const string &getText() const &" << endl;
        return m_text;
    }
    string &&getText() &&
    {
        cout << "string &&getText() &&" << endl;
        return move(m_text);
    }

private:
    string m_text;
};

调用:

TextHolder textHolder{"hello world"};
cout << textHolder.getText() << endl
     << endl;

cout << TextHolder{"hello world"}.getText() << endl;
cout << move(textHolder).getText() << endl;

执行结果:

const string &getText() const &
hello world

string &&getText() &&
hello world
string &&getText() &&
hello world

内联方法

c++提供了这样一种能力:可以建议函数或者方法的调用不在生成的代码中实现,就像调用独立的代码块那样。相反,编译器应将方法体直接插入调用方法的位置(编译阶段)。这个过程称为内联(inline),具有这一行为的方法称为内联方法。

可以在方法或函数定义的名称之前使用inline关键字,将某个方法或者函数指定为内联的。例如:

inline void print();

这提示编译器,用实际的方法体替换对print()的调用,而不是生成代码进行函数调用,注意:inline关键字只是提示编译器。如果编译器认为这会降低性能,就会忽略该关键字。

需要注意,在所有调用了内联函数或内联方法源文件中,内联方法或内联函数的定义必须有效。考虑这个问题,如果没有看到函数的定义,编译器如何替换函数体?因此,如果编写了内联方法,应该将方法定义在与其所在的类的定义放在同一文件中。

高级c++编译器不要求将内联方法的定义与类的定义放在同一个文件中。例如,Microsoft Visual C++支持链接时代码生成(Link-Time Code Generation,LTCG),会自动将较小的函数内联,哪怕这些函数没有声明为内联的或者没有在头文件中定义,也同样如此。GCC和Clang具有类似的功能。

在c++20模块之外,如果方法的定义直接放在类定义中,则该方法会隐式标记为inline,即使不使用inline关键字。对于c++20中模块导出的类,情况不再如此。如果希望这些方法是内联的,则需要使用inline关键字标记它们。

默认参数

C++中,默认参数与方法重载类似。在原型中可为函数或者方法的参数指定默认值,如果用户指定了这些参数,默认值就会失效。如果用户忽略了这些参数,将会使用默认值。但是存在一个限制,只能从最右边的参数开始提供连续的默认参数列表,否则编译器无法用默认参数匹配缺失的参数。默认参数可用于函数、方法和构造函数。示例:

class Spreadsheet
{
public:
    Spreadsheet(size_t width = 100, size_t height = 200){};
};

int main()
{
    Spreadsheet s1;
    Spreadsheet s2{7};
    Spreadsheet s3{8, 10};
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值