C++ 类和对象(下)

const成员

const 成员是指在类中声明的成员(变量或函数),其值或行为在对象的整个生命周期中都是不可修改的。这有助于确保对象的某些属性或行为不会被意外地更改,从而提高代码的可靠性和可维护性。

C++中有三种使用 const 的情况:const 成员变量、const 成员函数、const对象。

const成员变量

这是类中被声明为 const 的成员变量。一旦对象被创建,这些变量的值就不能被修改。const 成员变量必须在类的构造函数的成员初始化列表中进行初始化

class MyClass {
public:
    MyClass(int value) 
        : myConstValue(value) {}
    int getValue() const { 
        return myConstValue; 
    }

private:
    const int myConstValue;
};

const成员函数

const成员函数承诺不会修改对象的任何非 mutable 成员变量,并且可以在const对象上调用。这允许在const对象上执行只读操作。const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

class MyClass {
public:
	int getValue() const { return myValue; } // 不修改成员变量
	void setValue(int value) const { myValue = value; } // err 不能修改成员变量

private:
	int myValue;
};

需要注意的是,mutable 关键字可以用于修饰成员变量,即使在 const 成员函数内,这些被标记为 mutable 的变量仍然可以被修改。

class MyClass {
public:
    int getValue() const { return myValue; } // 不修改成员变量
    void setValue(int value) const { myValue = value; } //即使是const成员函数,也可以修改mutable成员变量
private:
    mutable int myValue;
};

const对象

  • const对象不能调用非const成员函数。
  • const对象不能修改其成员变量的值(除非这些成员变量被声明为mutable)。
class MyClass {
public:
    void setValue(int v) {
        value = v;
    }

private:
    int value;
};

int main() {
    const MyClass obj;
    // obj.setValue(10); // 错误: const对象不能调用非const成员函数
    return 0;
}

const成员的权限问题

问题:

1.const对象可以调用非const成员函数吗? 答:不可以。因为const对象的成员函数是const的,而非const成员函数不是const的,两者不匹配。如果强制调用,会导致编译错误。

2.非const对象可以调用const成员函数吗? 答:可以。因为非const对象的成员函数既可以是const的,也可以是非const的,两者匹配。

3.const成员函数内可以调用其他的非const成员函数吗? 答:不可以。因为const成员函数不能修改对象的状态,而非const成员函数可能会修改对象的状态,这样会导致编译错误。

4.非cosnt成员函数内可以调用其他的cosnt成员函数吗? 答:可以。因为非cosnt成员函数既可以是cosnt的,也可以是非cosnt的,两者匹配。

static成员

静态成员是类的成员,它们与类本身相关,而不是与类的实例(对象)相关。静态成员在所有类的实例之间共享,并且可以通过类名访问,而不需要创建类的实例。C++中有两种类型的静态成员:静态数据成员静态成员函数

静态成员变量

这是类的成员变量,它在类的所有实例之间共享。静态数据成员在类的声明中被声明为static,但是它们需要在类外部进行定义和初始化。通常,这是为了确保类的所有实例共享同一个数据。

#include <iostream>
using namespace std;
class MyClass {
public:
    void setStaticVar(const int &x) {
        staticVar = x;
    }
    int getStaticVar() {
        return staticVar;
    }

private:
    static int staticVar;// 静态数据成员的声明
};

int MyClass::staticVar = 0;// 静态数据成员的定义和初始化

int main() {
    MyClass obj1;
    MyClass obj2;

    //obj1.staticVar = 5;  //err,对private成员访问
    obj1.setStaticVar(5);
    std::cout << obj2.getStaticVar();// 输出为 0,因为静态数据成员在所有实例之间共享
    return 0;
}

注意: 这里静态成员变量staticVar虽然是私有,但是我们在类外突破类域直接对其进行了访问。这是一个特例,不受访问限定符的限制,否则就没办法对静态成员变量进行定义和初始化了。但是只限于对静态成员的定义和初始化。

静态成员函数

静态成员函数与类相关联,但不操作类的实例数据,因此它们没有隐式的this指针。静态成员函数可以通过类名直接调用,无需创建类的对象。静态成员函数没有隐藏的this指针,不能访问任何非静态成员。通常,它们用于执行与类相关的操作,不需要访问实例特定的数据。

class Test {
public:
    static void Fun() {
        cout << _a << endl;//error不能访问非静态成员
        cout << _n << endl;//correct
    }

private:
    int _a;       //非静态成员
    static int _n;//静态成员
};

小贴士:含有静态成员变量的类,一般含有一个静态成员函数,用于访问静态成员变量。(普通成员函数也可以访问静态成员)

访问静态成员变量的方法

当静态成员变量为公有时,有以下几种访问方式:

class Test {
public:
    static int _n;//公有
};
// 静态成员变量的定义初始化
int Test::_n = 0;

int main() {
    Test test;
    cout << test._n << endl;  //1.通过类对象突破类域进行访问
    cout << Test()._n << endl;//3.通过匿名对象突破类域进行访问
    cout << Test::_n << endl; //2.通过类名突破类域进行访问
    return 0;
}

当静态成员变量为私有时,有以下几种访问方式:

class Test {
public:
    static int GetN() {
        return _n;
    }

private:
    static int _n;
};
// 静态成员变量的定义初始化
int Test::_n = 0;

int main() {
    Test test;
    cout << test.GetN() << endl;  //1.通过对象调用成员函数进行访问
    cout << Test().GetN() << endl;//2.通过匿名对象调用成员函数进行访问
    cout << Test::GetN() << endl; //3.通过类名调用静态成员函数进行访问
    return 0;
}

静态成员和类的普通成员一样,也有public、private和protected这三种访问级别,所以当静态成员变量设置为private时,尽管我们突破了类域,也不能对其进行访问。

总结:

  1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
  2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
  3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员来访问
  4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制

注意区分两个问题:

1、静态成员函数可以调用非静态成员函数吗?

不可以。静态成员函数不能直接调用非静态成员函数。静态成员函数属于类本身,而不属于类的任何特定实例。因此,它们没有this指针来引用当前类的实例。非静态成员函数是与类的特定实例绑定的,因为它们可能访问或修改实例成员变量,所以必须通过一个类的实例来调用。

2、非静态成员函数可以调用静态成员函数吗?

可以。非静态成员函数可以直接调用静态成员函数。因为静态成员函数属于类本身,不需要类的实例就可以访问。由于非静态成员函数是在类的实例上下文中执行的,它们自然可以访问与类相关的所有静态成员,包括静态成员函数。

友元

友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。

友元分为:友元函数友元类

友元函数

友元函数是一种特殊类型的函数,它被允许访问类的私有成员,尽管这些函数不是类的成员。友元函数通常在类的声明中通过friend关键字进行声明,并且可以访问声明它为友元的类的私有和受保护成员。这种机制提供了对封装性的一定程度的破坏,但有时候它是必要的,例如用于实现操作符重载或者在某些特殊情况下的优化。

示例:

class Date {
    // 友元函数的声明
    friend ostream &operator<<(ostream &out, const Date &d);
    friend istream &operator>>(istream &in, Date &d);

public:
    Date(int year = 0, int month = 1, int day = 1) {
        _year = year;
        _month = month;
        _day = day;
    }

private:
    int _year;
    int _month;
    int _day;
};
// <<运算符重载
ostream &operator<<(ostream &out, const Date &d) {
    out << d._year << "-" << d._month << "-" << d._day << endl;
    return out;
}
// >>运算符重载
istream &operator>>(istream &in, Date &d) {
    in >> d._year >> d._month >> d._day;
    return in;
}

注意: 其中cout是ostream类的一个全局对象,cin是istream类的一个全局变量,<<和>>运算符的重载函数具有返回值是为了实现连续的输入和输出操作。

说明:

  • 友元函数可访问类的私有和保护成员,但不是类的成员函数
  • 友元函数不能用const修饰
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  • 一个函数可以是多个类的友元函数
  • 友元函数的调用与普通函数的调用原理相同

友元类

友元类是指一个类可以将另一个类声明为它的友元,从而允许友元类访问声明它为友元的类的私有和受保护成员。这在一些特殊情况下可能很有用,但需要慎重使用,因为它可能破坏类的封装性。

示例:

class FriendClass {
public:
    void display(const class MyClass& obj);
};

class MyClass {
private:
    int privateData;

public:
    MyClass(int data) : privateData(data) {}

    // 声明 FriendClass 为友元类
    friend class FriendClass;
};

// 在 FriendClass 中可以访问 MyClass 的私有成员
void FriendClass::display(const MyClass& obj) {
    std::cout << "FriendClass accessing private data: " << obj.privateData << std::endl;
}

int main() {
    MyClass obj(42);
    FriendClass friendObj;
    friendObj.display(obj);
    return 0;
}

友元类说明

1、友元关系是单向的,不具有交换性。
 例如上述代码中,B是A的友元,所以在B类中可以直接访问A类的私有成员变量,但是在A类中不能访问B类中的私有成员变量。
2、友元关系不能传递。
 如果A是B的友元,B是C的友元,不能推出A是C的友元。

3、友元关系不具有继承性,即如果类A是类B的友元,类B的子类不会自动成为类A的友元。

内部类

概念如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类, 它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越 的访问权限。

注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。

特性

  1. 内部类可以定义在外部类的public、protected、private都是可以的。
  2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
  3. sizeof(外部类)=外部类,和内部类没有任何关系。
#include <iostream>
using namespace std;

class A
{
private:
    static int k;
    int h;

public:
    class B // B天生就是A的友元
    {
    public:
        void foo(const A &a)
        {
            cout << k << endl;   // OK
            cout << a.h << endl; // OK
        }
    };
};

int A::k = 1;
int main()
{
    A::B b;
    b.foo(A());   // 编译器会将临时对象的成员变量的值赋上默认值0
}

输出结果

1
0

内部类的大小

class A//外部类
{
public:
    class B//内部类
    {
    private:
        int _b;
    };

private:
    int _a;
};

int main() {
    cout << sizeof(A) << endl;//外部类的大小:4
    return 0;
}

这里外部类A的大小为4,与内部类的大小无关。

匿名对象

C++ 匿名对象是指在没有被命名的情况下创建的临时对象。它们通常用于以下三种场景:

  • 以值的方式给函数传参,例如 Cat(); 会生成一个匿名对象,执行完后就会被析构。
  • 类型转换,例如 A a = 11; 相当于 A a = A(11);,这里的 A(11) 就是一个匿名对象。
  • 函数需要返回一个对象时,例如 return temp; 会先生成一个匿名对象,然后返回给调用者。

匿名对象的生命周期取决于是否有其他对象接收它。如果有,那么匿名对象的生命周期就变成了接收对象的生命周期;如果没有,那么匿名对象就会在使用完后立即被销毁。

#include <iostream>
using namespace std;

class A {
public:
    A(int a = 0)
        : _a(a) {
        cout << "A(int a)" << endl;
    }
    ~A() {
        cout << "~A()" << endl;
    }

private:
    int _a;
};

class Solution {
public:
    int Sum_Solution(int n) {
        //...
        return n;
    }
};

int main() {
    A aa1;
    //A aa1(); // 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
    // 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
    // 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
    A();
    A aa2(2);
    // 匿名对象在这样场景下就很好用,当然还有一些其他使用场景
    int a = Solution().Sum_Solution(10);
    cout << a << endl;
    return 0;
}

输出结果

A(int a)
A(int a)
~A()     // 生命周期一行的临时对象A()
A(int a)
10
~A()
~A()

拷贝对象时的一些优化

在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还是非常有用的。

#include <iostream>
using namespace std;

class A {
public:
    A(int a = 0)
        : _a(a) {
        cout << "A(int a)" << endl;
    }

    A(const A& aa)
        : _a(aa._a) {
        cout << "A(const A& aa)" << endl;
    }

    A& operator=(const A& aa) {
        cout << "A& operator=(const A& aa)" << endl;

        if (this != &aa) {
            _a = aa._a;
        }

        return *this;
    }

    ~A() {
        cout << "~A()" << endl;
    }

private:
    int _a;
};

void func1(A aa) {
}

// 引用传递,不调用任何构造
void func2(const A& aa) {
}

int main() {
    A aa1 = 1;   // 构造一个临时对象 + 拷贝构造 -> 优化为直接构造(不创建临时对象)
    func1(aa1);  // 无优化
    func1(2);   // 构造一个临时对象 + 拷贝构造 -> 优化为直接构造(不创建临时对象)
    func1(A(3));// 构造一个临时对象 + 拷贝构造 -> 优化为直接构造  

    //加引用就没有优化了,因为引用是别名,没有拷贝
    func2(aa1);
    //如果func2的参数不是const类型,会出现报错。aa1会创建一个临时对象,这个对象具有常属性,所以func2的参数要设置为const
    func2(2);    // 构造一个临时对象 + 拷贝构造 -> 优化为直接构造
    
    func2(A(3));  // 构造一个临时对象 + 拷贝构造 -> 优化为直接构造
    return 0;
}
// 创建一个A对象并返回它,会调用构造函数和拷贝构造函数
A func3() {
    A aa;     //构造
    return aa;//拷贝构造
}

//返回一个匿名对象,会调用构造函数和拷贝构造函数。
A func4() {
    return A();
}

int main() {
    func3();          //构造 + 拷贝构造 -> 优化为直接构造

    A aa1 = func3();    //构造 + 拷贝构造 + 拷贝构造 -> 优化为直接构造
    func4();          //构造 + 拷贝构造 ->  优化为拷贝构造
    A aa2 = func4();   //构造 + 拷贝构造 + 拷贝构造 -> 优化为直接构造
    return 0;
}

对象返回总结:

1、接收返回值对象,尽量拷贝构造方式接收,不要赋值接收(赋值是已经定义了对象,在赋值,编译器无法优化)

2、函数中返回对象时,尽量返回匿名对象

函数传参总结: 尽量使用const &传参

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值