<C++> 类和对象(下)

这篇具有很好参考价值的文章主要介绍了<C++> 类和对象(下)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1.const成员

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

C++中有两种使用 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成员的权限问题

问题:

  1. const对象可以调用非const成员函数吗? 答:不可以。因为const对象的成员函数是const的,而非const成员函数是不const的,两者不匹配。如果强制调用,会导致编译错误。
  2. 非const对象可以调用const成员函数吗? 答:可以。因为非const对象的成员函数既可以是const的,也可以是非const的,两者匹配。
  3. const成员函数内可以调用其他的非const成员函数吗? 答:不可以。因为const成员函数不能修改对象的状态,而非const成员函数可能会修改对象的状态,这样会导致编译错误。
  4. 非cosnt成员函数内可以调用其他的cosnt成员函数吗? 答:可以。因为非cosnt成员函数既可以是cosnt的,也可以是非cosnt的,两者匹配。

2.static成员

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

静态数据成员

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

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

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

int main() {
    MyClass obj1;
    MyClass obj2;

    obj1.staticVar = 5;
    std::cout << obj2.staticVar;  // 输出为 5,因为静态数据成员在所有实例之间共享
    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、静态成员函数可以调用非静态成员函数吗?
2、非静态成员函数可以调用静态成员函数吗?
问题1:不可以。因为非静态成员函数的第一个形参默认为this指针,而静态成员函数中没有this指针,故静态成员函数不可调用非静态成员函数。
问题2:可以。因为静态成员函数和非静态成员函数都在类中,在类中不受访问限定符的限制。

3.友元

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

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

友元函数

友元函数是一种特殊类型的函数,它被允许访问类的私有成员,尽管这些函数不是类的成员。友元函数通常在类的声明中通过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的友元。

4.内部类

内部类是一个类定义在另一个类的内部的类。也就是说,一个类可以在另一个类的内部声明和定义一个类,被声明的类被称为内部类。内部类可以访问外部类的成员,包括私有成员,因为它们被视为外部类的友元

注意
 1、此时的内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象区调用内部类。
 2、外部类对内部类没有任何优越的访问权限。
 3、内部类就是外部类的友元类,即内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。

特性
 1、内部类可以定义在外部类的public、private以及protected这三个区域中的任一区域。
 2、内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。
 3、外部类的大小与内部类的大小无关。

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

private:
    int _a;
};

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

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

5.匿名对象

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

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

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

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);
    // 匿名对象在这样场景下就很好用,当然还有一些其他使用场景,这个我们以后遇到了再说
    Solution().Sum_Solution(10);
    return 0;
}

6.拷贝对象时的一些优化

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

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));// 构造+拷贝构造 -> 优化为直接构造   A(3)构造  传参拷贝构造

    // 加引用就没有优化了,因为引用是别名,没有拷贝
    func2(aa1);
    func2(2);// 如果func2的参数不是const类型,会出现报错。aa1会创建一个临时对象,这个对象具有常属性,所以func2的参数要设置为const
    func2(A(3));
    return 0;
}
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 &传参文章来源地址https://www.toymoban.com/news/detail-672697.html

到了这里,关于<C++> 类和对象(下)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++】:类和对象(1)

    朋友们、伙计们,我们又见面了,本期来给大家解读一下有关C++中类和对象的知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成! C 语 言 专 栏: C语言:从入门到精通 数据结构专栏: 数据结构 个  人  主  页 : stackY、 目录 1.面向过程和面

    2024年02月08日
    浏览(27)
  • 【C++】类和对象-封装

    在main函数前重新补上isSame函数 在Cube类里面添加issamebyclass,利用成员函数判断两个立方体是否相等 自己写的代码: B站视频链接: https://www.bilibili.com/video/BV1et411b73Z/?p=105spm_id_from=333.1007.top_right_bar_window_history.content.clickvd_source=fb8dcae0aee3f1aab700c21099045395

    2024年02月15日
    浏览(34)
  • 【c++】类和对象1

    C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。 C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完 成 C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。

    2024年01月23日
    浏览(24)
  • 【C++】类和对象(一)

    目录 🍊一.面向过程和面向对象的初步认识🍊 🍋二.类的引入🍋 🍍三.类的定义🍍 🥭四.类的访问限定符及封装🥭 4.1访问限定符 4.2class和struct的区别 4.3封装 🍎五.类的作用域🍎 🍏六.类的实例化🍏 🍐七.类对象模型🍐  7.1怎么计算类对象的大小 7.2类对象的存储方式 🍑

    2024年02月05日
    浏览(23)
  • 【C++】类和对象(3)

    首先我们先回顾一下构造函数,对象的初始化由构造函数来完成,我们可以在构造函数的函数体内对对象的成员变量进行赋值,但这就有一个问题,如下: 答案:显然不是,因为变量只能定义一次。 也就是说,构造函数的函数体内部并不是初始化的地方(定义的地方),而

    2024年02月06日
    浏览(25)
  • 【C++】类和对象(下)

    在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。 虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化, 构造函数体中的语句只能将其称为赋初值 ,而不能称作初始化。因为初始化只能

    2024年03月25日
    浏览(30)
  • 【C++】类和对象(上篇)

    🚩 C语言 是 面向过程 的, 关注 的是 过程 ,分析出求解问题的步骤,通过函数调用逐步解决问题 🚩 C++ 是基于 面向对象 的, 关注 的是 对象 ,将一件事情拆分成不同的对象,靠对象之间的交互完成 🌰 比如: C语言结构体中只能定义变量, 在C++中,结构体内不仅可以定

    2024年01月22日
    浏览(27)
  • C++:类和对象(中)

    如果一个类中什么成员都没有,简称为空类。 空类中真的什么都没有吗?并不是,任何类在什么都不写时, 编译器会自动生成以下6个默认成员函数。 默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数 class Date {}; 对于Date类,可以通过 Init 公有方

    2024年02月03日
    浏览(28)
  • C++类和对象(上)

    ✨Blog:🥰不会敲代码的小张:)🥰 🉑推荐专栏: C语言 🤪、 Cpp 😶‍🌫️、 数据结构初阶 💀 💽座右铭:“ 記住,每一天都是一個新的開始😁😁😁 ” 💀本章内容: 《C++类和对象(上)》的介绍✨ C语言是面向过程的,关注的是过程,分析出求解的步骤,通过函数逐步解

    2024年01月25日
    浏览(19)
  • 【C++】类和对象(2)

    这篇博客继续学习类和对象~,主要介绍了类的6个默认成员函数。 目录 类的6个默认成员函数  构造函数 概念  特性 析构函数 概念 特性 拷贝构造函数 特性 赋值运算符重载 运算符重载 赋值运算符重载  前置++和后置++重载 日期类的实现 const成员 取地址及const取地址操作符重

    2024年02月20日
    浏览(18)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包