c++(2)

C++ (2)

1. 类与对象

1.1 类与对象的概念

从c和c++的struct开始,c的struct结构体只能存在数据变量,而c++的struct体可以函数

1.1.1 类的封装

通过类可以封装对象的属性(特征,数据变量或引用)、行为(函数,类方法),可以通过访问权限(公开 public,私有的private,受保护protected)设置属性和行为的访问性。私有的或手保护的权限的(数据或方法)可以通过公开的方法(行为)进行操作。

类的封装主要的任务:

1. 把变量(属性)和函数(操作)合成一个整体,封装一个类中
2. 对变量和函数进行访问控制

image-20231114162504828

public修饰的成员,在任何地方都可以访问。类的内部,所有的成员都可以访问,如果不使用子类的情况下,protected和private一样的。如果存在子类,则protected成员可以被访问。

定义类的语法:

class 类名{
    [访问权限:]
    //封装类成员的属性
    //封装类成员的方法
};

【注意】类的定义是建议在全局区或头文件中

1.1.2 类的实例

类定义好之后,可以通过类,创建具体的对象,创建的类对象又称之为类的实例。

通过类创建的对象,对象即具有类的属性(数据)和方法

语法:

类名 对象名;

如:

#include <iostream>
#include <cstring>
using namespace std;

class Student
{
public:
    int sid;
    char name[32];
private:
    //如果成员变量初始化值时,编译时指定标准为c++11
    float score = 0;
public:
    void showScore()
    {
        cout << "sid=" << sid << ",score=" <<score <<endl;
     }
    void addScore(float n);//只是声明
};
//实现类中声明的方法(函数)
void Student::addScore(float n)
{
    score = n;
}
int main(int argc,char const *argv[])
{
    Student s1;
    s1.sid = 1001;
    strcpy(s1.name,"disen");
    //s1.score = 100;不能直接访问private成员
    s1.addScore(100);//通过公开的方法修改private成员
    s1.showScore();
    return 0;
}

创建类对象时,在栈区分配内存空间,存放对象的属性(成员变量),当对象调用方法时,除了实参的数据之外,默认存在this指针,this指向调用的对象地址(内存空间),在方法中通过this指针可以操作对象自己的属性(成员变量)。

1.2 对象的构造与析构

1.2.1 构造函数与析构函数

对象的构造:创建类的对象时,默认调用类的某一个函数,进行内存空间的创建和初始化类的成员变量的值,这个函数称之为构造函数。

构造函数:无返回值(不能使用void),函数名同类名。默认的构造函数是无参函数。

对象的存储空间在栈区时,程序结束时,自动释放空间,在释放空间之前,会调用对象的析构函数,回收资源。

析构函数:无返回值,无参数,函数名同类名,但名称前加~标识符。

【注意】构造函数可以存在多个(具有重载特性),析构函数只能有一个。构造函数和析构函数的访问权限尽量是public的。

1.2.2 构造函数的分类与调用

按参数类型:分为无参构造函数和有参构造函数

按类型分类:普通构造函数和拷贝构造函数(复制构造函数)

拷贝的构造函数:接收同类的其他对象,将其它对象中的数据复制到当前对象中。

【注意】如果没有显示地声明构造函数,则编译器自动添加一个无参的构造函数(隐式)。如果显示地声明构造函数,则编译器不会提供无参的构造函数。如果显示地声明构造函数,也应该提供一个无参的构造函数,可供创建类对象使用(类名 对象名)。

构造函数的调用方式:

1)无参的调用
   类名 对象名;
2)有参的调用
   类名 对象名(参数列表);
3)匿名(无对象名) 调用
   类名(参数列表) //创建了对象,但是没有名称
   类名 对象名 = 类名(参数列表);
4)= 调用,针对单个参数的构造函数
   类名 对象名 = 常量(字面量) //等价于 类名 对象名=类名(常量);
   类名 对象名 = 同类的其他对象; //等价于 类名 对象名=类名(同类其它对象)
5new方式
   类名 *对象名 = new 类名(参数列表);
   free(对象名)·

【注意事项】

1)创建的类的对象,如果是指针类型,则需要手动释放(free、调用析构函数、delete)
2)对于直接通过类创建的对象,不能手动释放,在对象的作用域之外自动释放(调用析构函数)

1.2.3 拷贝构造函数的调用时机

拷贝构造函数的调用时机:

1)类对象作为右值,赋值给定义类的对象时,会调用拷贝构造函数
2)函数的局部对象直接返回时,可能会调用构造函数
3)类对象作为函数的参数(以值的方式传递)时,可能会调用构造函数

当函数返回一个局部对象时,编译器做了优化,在函数外可以接收的(转化为引用),如果手动声明返回类型为对象的引用,反而会导致局部的对象在返回之后自动释放空间,使得外部使用时报段错误。

1.2.4 构造函数调用规则

默认情况下,c++编译器至少为我们写的类增加3个函数

1.默认构造函数(无参,函数体为空)

2.默认析构函数(无参,函数体为空)

3.默认拷贝构造函数,对类中非静态成员属性简单值拷贝

如果用户定义拷贝构造函数,c++不会再提供任何默认构造函数,如果用户定义了普通构造(非拷贝),c++不再提供默认无参构造,但是会提供默认拷贝构造。

1.2.5 深拷贝与浅拷贝

浅拷贝:只复制一层内存空间

image-20231115083538356

深拷贝:所有层次空间全部复制一份

image-20231115083611892

1.2.6 多个对象构造和析构

1.2.6.1 初始化列表

构造函数:主要用于创建类的对象,在定义构造函数时,C++中提供了初始化列表的语法,用于初始化成员变量的值。

类名(参数列表):成员名(参数名),成员名2(参数名2),...{}
1.2.6.2 类对象作为成员
#include <iostream>
using namespace std;
class A
{
public:
    A()
        {
            cout << "A()" << endl;
        }
    A(int x)
        {
            cout << "A(int)" << endl;
    }
    ~A()
    {
        cout << "~A()" << endl;
    }
};
class B
{
public:
    B() // 构造函数用于初始化成员数据, 说明成员变量先创建
    {
        cout << "B()" << endl;
    }
    //B(int x):a(x) //指定a对象的构造函数进行数据初始化
    B(int x)
    {
        cout << "B(int)" << endl;
    }
    ~B() // A的对象是B的空间中,A是B空间释放之后就失效,也会释放空间
    {
        cout << "~B()" << endl;
    }
private:
    A a; // A类的对象作为B类的成员
};
int main(int argc, char const *argv[])
{
    B b1;
    // 输出什么?
    return 0;
}

image-20231115085014548

说明:

1)创建B空间时,成员变量a的内存空间会自动创建
2)当成员变量a的空间创建完成后,再调用构造函数,进行数据初始化
3)当有B空间释放时,成员变量a的作用域失效,则也会释放空间

1.2.7 explicit关键字

c++提供了关键字explicit,禁止通过构造函数进行的隐式转换(格式: 类 对象=值)。

声明为explicit的构造函数不能在隐式转换中使用。构造函数的参数只能存在一个或第一个没有默认值(其它参数都具有默认值)的情况

1.2.8 动态创建对象

动态创建对象方式:

1)malloc,realloc,calloc在堆中创建空间,使用完之后,需要手动释放free()
当内存空间创建完之后,进行数据的初始化或者调用自定义的初始化函数(默认不会调用构造函数,构造函数不能显示调用)
2new关键字创建对象空间
使用new在堆中创建空间之后,自动调用对象的构造函数进行数据初始化
使用new创建的对象,可以通过delete关键字来调用对象的析构函数释放内存

1.2.9 扩展new和delete

用于数组的new和delete

创建数组:  堆空间中创建
数据类型 *指针变量名 = new 数据类型[个数];
数据类型 *指针变量名 = new 数据类型[个数]{元素,...};

删除new创建的数组:
delete[] 指针变量名;

动态创建类对象的数组时,如果没有指定类对象的创建方式时,必须提供一个无参的构造函数:

类名 *p = 类名[个数];
类名 *p = 类名[个数]{类名(参数列表),...};

2. 类的静态成员

类的成员声明时,前面(最左边)可以使用static关键字,将其声明为类的静态成员。

静态成员:静态成员变量和静态成员函数

【注意】类的静态成员属于类,不占类对象的空间,是所有类对象共享的。类的静态成员函数,只能访问静态成员变量。

静态成员的声明、初始化与访问

#include <iostream>
using namespace std;

class A
{
public:
    static int x;
};
int A::x = 20;//类中声明类外定义
int main()
{
    A a1,a2;
    //访问类的静态成员:1)A::x,2)对象名.x
    cout << "A::x=" << A::x <<endl;
    A::x = 30;
    cout << "a1.x=" << a1.x << endl;
    cout << "a2.x=" << a2.x << endl;
    return 0;
}

image-20231115095328848

class A
{
public:
    int m;
    static int x;
    static void add(int n)
    {
        // m = 10; // error, 静态成员函数不能访问非静态成员
        x += n;
        cout << "static x=" << x << endl;
    }
};

类的单例设计模式

私有化构造函数和拷贝函数,提供一个public的静态函数返回类的指针对象,声明一个静态的类的指针对象

#include <iostream>
#include <cstdin>
using namespace std;

class A//单例的类,类的对象运行期间有且只有一个对象
{
private:
    int x;
    A(int x){this->x=x;}
    A(const A &other)
    {
        x=other.x;
    }
public:
    static A *getInstance(int x=0)
    {
        
        if(instance == NULL)
        {
            instance = new A(x);
        }
        return instance;
    }
    void show()
    {
        cout << "x=" << x <<endl;
    }
public:
    static A *instance;
};
A *A::instance = NULL;//定义初始化,在静态全局区
int main()
{
    A *a1 = A::getInstance();
    a1->show();
    A*a2 = A::getInstance();
    a2->show();
    cout << "a1 addr=" << a1 << endl;
    cout << "a2 addr=" << a2 << endl;
    delete A::instance;//释放单例对象的空间
    return 0;
}

image-20231115101643820

3. 对象的存储

3.1 类的成员变量与函数的存储

类中的非静态成员存储与类的对象存在一起的,类的成员函数存储在类对象(代码区)。

一个类的大小由类的非静态成员的决定的。

当前调用成员函数时,从类存储的位置(代码区)中调入的栈中,为函数的形参变量分配空间(局部变量),同时也会调用成员函数的对象封装成当前类的指针,这个指针即为this。

3.2 this指针

image-20231115102057358

当某一个对象调用成员函数时,将对象转化为this指针,传入到成员函数中,以区分另一个对象调用的成员函数,方便操作自己对象的成员变量。当形参和成员变量同名时,也可用this指针来区分。

在类的非静态成员函数中返回对象本身,可使用return *this,可以通过这种实现对象的链式编程(a1.a().b().c()...😉,如cout属于链式编程风格(cout<<""<<""<<"").

3.3 const修饰成员函数与类对象

当const修饰成员函数时,函数内不能修改普通成员变量,但可以修改mutable修饰的成员变量。

当const修饰类对象时,只能读成员变量,不能修改成员变量的值,可以调用const修饰的成员函数,不能调用普通成员函数。

4. 友元

4.1 友元语法

使用friend关键字,可以修饰其他类、类成员函数,全局函数都可声明为友元

【注意】

友元函数不是类的成员,不带this指针
友元函数可访问对象任意成员属性,包括私有属性

4.2 全局友元函数

class B
{
    //声明全局函数的友元
    friend void show(B &b);//将全局的show()函数设置为当前类的友元函数
private:
    int x;
public:
    B(int x)
    {
        this->x=x;
    }
    void show()
    {
        cout <<x <<endl;
    }
};
//全局的友元函数
void show(B &b)
{
    //b的私有成员
    cout << "b.x="<<b.x<<endl;
}

4.3 友元的类成员函数

类的某一个成员函数作为友元函数时,不能在类定义,只能声明

class B;
class C
{
public:
    //在此函数内,要访问B类中所有成员,将此函数在B类中声明友元
    //先声明,不能实现
    void showB(B &b);
};
class B
{
    //声明友元的成员函数
    friend void C::showB(B &b);
private:
    int x;
public:
    B(int x)
    {
        this->x=x;
    }
    void show()
    {
        cout<<x<<endl;
    }
};
//定义或实现友元的成员函数
void C::showB(B &b)
{
    cout <<b.x<<endl;
}

4.4 友元类

class B;
class C
{
public:
    void showB(B &b);
};
class B
{
    friend class C;
private:
    int x;
public:
    B(int x)
    {
        this->x=x;
    }
    void show()
    {
        cout <<x <<endl;
    }
};
//定义或实现友元的成员函数
void C::showB(B &b)
{
    cout << "b.x="<<b.x<<endl;
}
posted @   常羲和  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
// 侧边栏目录
点击右上角即可分享
微信分享提示