当前位置: 首页 > news >正文

C++类和对象入门(三)

目录

前言

一、初始化列表

1.1定义

1.2 格式和语法

1.3与在函数内初始化的区别

1.4使用初始化列表的必要性

1.5成员变量默认值的使用(C++11)

 1.6初始化的先后顺序

1.7初始化列表的总结

二、类型转换

2.1内置类型转化成类类型

2.2类类型之间的相互转换

2.3explicit关键字

2.3.1explicit关键字在内置类型与类类型转换

2.3.2explicit关键字在类类型与类类型转换

三、static成员变量

3.1static变量的特点

3.2static成员函数

3.3static成员函数的访问

3.4static成员变量的初始化

3.5 访问控制与静态成员

3.7static成员函数和成员变量的总结      

结语


前言

        朋友们好,今天在这里继续贯穿C++类和对象的基础知识讲解,大致包括三点:

        初始化列表,类型转换,static成员详解

        那么接下来就让我们一起进入学习吧!


一、初始化列表

1.1定义

        初始化列表是在类的构造函数中,对成员进行初始化的一种特殊机制,初始化列表可以提高效率,尤其是对于一些特殊类型的变量,他是唯一的初始化方式(这个我们后面会讲到)

1.2 格式和语法

        语法:初始化列表是在对应构造函数后面添加一个冒号,接着各个需要初始化的成员以逗号隔开,每个成员的后面都紧跟括号中的初始值或者表达式,例如:

class Myclass
{
public:
    Myclass(int a, int b)
    ;_a(a)
    ,_b(b)
private:
    int _a;
    int _b;
}

        上面就是直接给类中的两个成员使用初始化列表进行初始化。

        那么问题来了,在构造函数体内部同样可以对成员进行初始化,那么两者的区别和用法又有什么区别呢?

1.3与在函数内初始化的区别

  • 内置类型:对于内置类型(int , char)等类型的变量,使用初始化列表初始化和在函数体内初始化的效率和结果是一模一样的。
  • 类类型:对于类类型的成员,如果没有使用初始化链表进行初始化,那么成员变量会先调用默认构造函数进行初始化,再在函数体内进行赋值,相当于两次初始化操作,例如如下代码:
class Member {
public:
    Member(int value = 5) : _value(value) {}
private:
    int _value;
};

class A {
public:
    A(int x) {
        _member = Member(x);  // 先默认构造后再赋值
    }
private:
    Member _member;
};

        上面这个例子,_member会先调用Member的默认构造函数,再在函数体通过X进行赋值,使用初始化列表完全可以写成这样,如下:

class A {
public:
    A(int x) : _member(x) {  // 直接通过初始化列表初始化
    }
private:
    Member _member;
};

        这样_member只初始化了一次,大大减小了性能的开销。 

1.4使用初始化列表的必要性

  • 效率问题:再函数如果出现内部开销比较大,或者内部逻辑不清晰的情况下,使用初始化列表一是可以提高函数的执行效率避免二次初始化,二是可以使得函数的逻辑更加清晰。
  • 必须的情况:对于某些成员变量,如const类型引用类型没有构造函数的类类型成员必须通过初始化列表进行初始化,在函数体内对这些成员进行初始化是不被允许的。
class Time 
{
public:
    Time(int hour)
    :_hour(hour)
private:
    int _hour;
}

class Myclass 
{
public:
    Myclass(int a,int& b)
    :_a(a)//_a是一个const类型变量,初始化的时候必须使用初始化列表
    ,_b(b)//_b是一个引用类型变量,初始化的时候必须使用初始化列表
    ,_t(12)//_t是一个类类型变量,初始化的时候必须使用初始化列表
private:
    const int _a;
    int& _b;
    Time _t;   
}

1.5成员变量默认值的使用(C++11)

        在C++11中引入了变量默认值的概念,如果成员变量没有在初始化列表中被显式初始化,那么在这个成员变量在声明的时候,可以通过为它提供一成员变量的默认值来弥补。

class Myclass
{
public:
    Myclass(int a, int b)
    :_a(a)
private:
    int _a;
    int _b = 1;//默认值
}

 1.6初始化的先后顺序

        尽管在初始化列表中,成员的位置可以随意先后出现,但是他的初始化顺序仍然是按照声明中的顺序进行初始化的。 

class Myclass
{
public:
    Myclass(int a , int b):_a(a),_b(b)//这里初始化的顺序是依照下面的先b后a
private:
    int _b;
    int _a;
}

        但是,为了保证代码逻辑的一致性,建议初始化列表的顺序与声明的顺序相同。

1.7初始化列表的总结

  • 每个函数都有初始化列表,即使你没有显式地写出他。
  • 每个成员变量都必须被初始化,即使他没有在初始化列表中显式地初始化。
  • 对于引用类型,const类型,没有默认构造函数的类类型成员,必须在初始化列表中进行初始化。
  • C++11 允许在成员变量声明时提供默认值,这些默认值会在初始化列表中未显式初始化时使用。
  • 初始化顺序取决于成员变量在类中的声明顺序,而不是它们在初始化列表中的顺序。

二、类型转换

        在C++里,类型转换是将一个类型的数据转化成另一个类型的过程。对于类而言,C++允许将内置类型类类型转化成其他类型。

        对于类型转换,可以是显式的,也可以是隐式的,这里面涉及3个知识点:构造函数转换运算符explicit关键字。

2.1内置类型转化成类类型

        C++中,可以通过定义带有内置类型参数的构造函数来将内置类型转化成类类型。

class Myclass
{
public:
    Myclass(int a)
:_a(a)
private:
    int _a;
}

int main()
{
    Myclass 10;//将10隐式地转化成了Myclass的类类型
}

        这里的Myclass = 10就是隐式类型转化的常见形式。

2.2类类型之间的相互转换

        如果遇到了一个场景需要类B接受类A的一个类对象,当我们将类A的值直接赋值给B的时候,就会发生下面的类与类之间的隐式类型转换。

class A
{
public:
    A(int a1)
    :_a1(a)
int Get()const{ return _a;}//const修饰,前文讲过,为了防止权限放大
    private:
    int _a;
}
class B
{
public:
    B (const&A a):_b(a.Get())
private:
    int _b;
}
int main()
{
    A obj(10);
    B obj = A obj;//A类型对象隐式转换成B类型
}

        这是类类型之间的隐式类型转换的常见形式。

2.3explicit关键字

        explicit关键字的作用是防止在隐式类型转换的时候,发生不必要的逻辑错误,用explicit关键字来修饰构造函数,这样可以禁止该构造函数参与隐式类型转换。

2.3.1explicit关键字在内置类型与类类型转换

class Myclass
{
public:
    explicit Myclass(int a)
:_a(a)
private:
    int _a;
}

int main()
{
    Myclass = 10;//不能这样写,explicit修饰的构造函数不能参与隐式类型转化
    Myclaa (10);//正确,需要显式调用构造函数
}

2.3.2explicit关键字在类类型与类类型转换

class A
{
public:
    explicit A(int a1)
    :_a1(a)
int Get()const{ return _a;}//const修饰,前文讲过,为了防止权限放大
    private:
    int _a;
}
class B
{
public:
    explicit B (const&A a):_b(a.Get())
private:
    int _b;
}
int main()
{
    A objA(10);
    B objB = A obj;//不能这样写,explicit阻止了隐式类型转换
    B objB(objA);
}

三、static成员变量

        static成员变量,静态变量,他是类的所有对象共享的变量,而不是每个对象独立拥有的。

        静态变量只能存储在静态存储区,也就是全局范围内,并且只能在外部初始化。

3.1static变量的特点

  1. 共享:他不是一个对象专属的,二十所有类的对象都可以共享的。
  2. 独立性:static变量会储存在静态存储区,不会随着对象爱那个的创建和销毁来重新分配内存。
  3. 类外初始化:静态城院变量的初始化只能在类外初始化,不能在类内声明的位置给它赋值。
#include<iostream>
using namespace std;

class A {
public:
    A() {
        ++_scount;  // 每创建一个对象,计数加1
    }

    A(const A& t) {
        ++_scount;  // 每调用拷贝构造函数,计数加1
    }

    ~A() {
        --_scount;  // 每销毁一个对象,计数减1
    }

    static int GetACount() {
        return _scount;  // 返回当前对象的数量
    }

private:
    // 声明静态成员变量
    static int _scount;
};

// 类外初始化静态成员变量
int A::_scount = 0;

int main() {
    cout << "初始对象数量: " << A::GetACount() << endl;  // 初始对象数量为 0
    A a1, a2;  // 创建两个对象,计数加2
    A a3(a1);  // 拷贝构造,计数加1
    cout << "当前对象数量: " << A::GetACount() << endl;  // 输出 3

    return 0;
}

        如前文所述,静态成员变量不单独属于某个对象,每个类A的对象在创建的时候都会给_scount加一,每销毁一个对象就会减一。


3.2static成员函数

        静态成员函数是类里面的一个特殊的成员函数,它并不依赖具体的对象实例,可以通过类明直接调用,静态成员函数没有this指针,因此它只能访问类的静态成员变量或者静态成员函数不能访问非静态成员

示例:

#include<iostream>
using namespace std;

class A {
public:
    A() {
        ++_scount;  
    }

    A(const A& t) {
        ++_scount;  
    }

    ~A() {
        --_scount; 
    }

    static int GetACount() {
        return _scount;  // 静态成员函数
    }

private:
    // 声明静态成员变量
    static int _scount;
};

// 类外初始化静态成员变量
int A::_scount = 0;

int main() {
    A a1, a2;  // 创建两个对象,计数加2
    A a3(a1);  // 拷贝构造,计数加1
    cout << "当前对象数量: " << A::GetACount() << endl;  // 通过类名直接访问静态成员函数

    return 0;
}


3.3static成员函数的访问

        上文我们已经讲过一种访问方式:通过类名访问。但是其实还有一种访问方式:通过对象访问

int main() {
    cout <<  A::GetACount() << endl;  // 通过类名直接访问静态成员函数
    cout <<  a1.GetACount() << endl;
    return 0;
}

        其实二者的本质是相同的,都是通过类来访问静态成员函数。

3.4static成员变量的初始化

        静态成员变量不能再类内进行初始化,只能在类外进行初始化。

        因为静态成员变量储存在静态区中,而不是在对象中,由于静态成员变量的共享性,它们只在整个程序中存在一份,因此必须在类外进行初始化,以确保所有对象访问的都是同一份数据。

3.5 访问控制与静态成员

        静态成员与普通成员一样,也受访问控制修饰符publicprotectedprivate)的限制。即使静态成员属于类,而不是对象,但它们仍然需要遵守访问控制规则。

class A {
private:
    static int _private_count;

public:
    static int GetPrivateCount() {
        return _private_count;  // 只能通过成员函数访问
    }
};

int A::_private_count = 0;

int main() {
    // cout << A::_private_count;  // 错误,无法访问 private 静态成员
    cout << A::GetPrivateCount() << endl;  // 通过静态成员函数访问
    return 0;
}

3.7static成员函数和成员变量的总结      

        在C++中,static成员为类提供了管理全局数据和类级别操作的强大机制。静态成员变量被所有对象共享,存储在静态存储区中,而静态成员函数则可以在没有对象的情况下通过类名直接调用。静态成员与普通成员一样,受访问控制修饰符的限制,可以是public、private或protected。同时,静态成员变量不能在类内初始化,必须在类外进行初始化。通过静态成员,我们可以方便地实现对象计数、全局状态管理等功能,这让类在不依赖对象实例的情况下,依然能够提供有用的功能。


结语

        至此,本文介绍的关于C++入门的部分知识正式结束,日后我会更新更多关于C++的基础入门知识,如果本文能帮助到阅读文章的你,就请点赞转发收藏吧,您的支持也是我继续学习和更新的动力,感谢支持!!

        

相关文章:

  • uniapp uniCloud引发的血案(switchTab: Missing required args: “url“)!!!!!!!!!!
  • ESP8266TCP客户端(单连接TCP Client)
  • java项目springboot 项目启动不了解决方案
  • 线性表相关代码(顺序表+单链表)
  • Python深度学习算法介绍
  • 基于SpringBoot的历史馆藏系统设计与实现(源码+SQL脚本+LW+部署讲解等)
  • JDK 的 SPI 和 Dubbo 的 SPI
  • vector模板类的模拟实现
  • JVM垃圾回收面试题及原理
  • 代码随想录二刷|图论4
  • 实现一个日期类(类和对象实践项目)
  • 使用 potrace.js实现图像矢量化教程
  • Windows控制台函数:标准输入输出流交互函数GetStdHandle()
  • 基于Spring Boot的城市垃圾分类管理系统的设计与实现(LW+源码+讲解)
  • 使用 Python 开发的简单招聘信息采集系统
  • 人工智能里的深度学习指的是什么?
  • Next.js 的基本了解
  • 【工具使用】IDEA 社区版如何创建 Spring Boot 项目(详细教程)
  • 蓝耘赋能通义万相 2.1:用 C++ 构建高效 AI 视频生成生态
  • CSS定位布局-五个定位实现自由布局(Static, Relative, Absolute, Fixed, Sticky)
  • 自己怎么优化网站排名/360搜索推广官网
  • 宿迁哪家做网站推广/百度排名优化咨询电话
  • 泰安企业建站公司哪里找/长尾关键词查询
  • 基于mysql的网站用什么做/培训公司排名
  • 可以用手机做网站吗/职业培训机构哪家最好
  • 零基础学平面设计怎么学/百度关键词优化有效果吗