1.7 cpp中类的常见特性


返回目录 1 面向对象技术
上一节 1.6 cpp的常见特性
下一节 1.8 继承


public和private

pubulic:公有访问限定符,具有类外交互能力,类内部外部都可使用;

private:私有访问限定符,仅允许本类中函数访问,不可在类外使用;

protected:保护访问限定符,仅允许本类及本类的派生类访问。

源代码

/* publicAndPrivate.cpp 公有私有访问权限实例 */
#include <iostream>
#include <string>

class ExampClass
{
private:
    std::string priData;
public:
    ExampClass(); // 默认构造函数,不作修改
    ~ExampClass(); // 默认析构函数,不作修改
    std::string pubData;
    void addData(std::string priData, std::string pubData); // 添加数据
    void displayPriInfo(); // 显示私有成员
};

ExampClass::ExampClass()
{
    /* 构造函数里空空如也 */
}

ExampClass::~ExampClass()
{
    /* 析构函数里空空如也 */
}

void ExampClass::addData(std::string priData, std::string pubData)
{
    this->priData = priData;
    this->pubData = pubData;
}

void ExampClass::displayPriInfo()
{
    std::cout << priData << std::endl;
}

int main()
{

    ExampClass exobj;
    exobj.addData("这是私有数据", "这是公有数据");

    exobj.displayPriInfo(); // 显示刚刚添加的数据,我们可以用类内部函数访问公有和私有成员
    std::cout << exobj.pubData << std::endl << std::endl; // 显示类中的公有成员

    // exobj.priDate = "我修改了私有数据"; // <-- 这是错误的,无法直接访问私有访问权限的成员
    exobj.pubData = "我修改了公有数据"; // 这是正确的,我们可以直接访问和修改公有访问权限的成员

    exobj.displayPriInfo(); // 输出
    std::cout << exobj.pubData << std::endl;

    return 0;

}

编译运行

这是私有数据
这是公有数据

这是私有数据
我修改了公有数据

构造函数和析构函数

1.5中,我们提到了类中的构造函数析构函数

构造函数会在类实例化时自动运行,默认是不作任何处理,这个函数可以重载、添加参数等,一般在构造函数中进行一些初始化操作;

析构函数则会在对象生命周期结束以后自动运行,无法重载和添加参数,一般在析构函数中进行清理操作(如释放内存空间)。

对象的生命周期可以参照变量的生命周期,但是指针申请的内存无法被自动清理,需要我们手动清理

构造函数的名字是类名,析构函数则在构造函数前加上取反符号'~'。

源代码

/* ConstructedAndDestrcutor.cpp 构造函数与析构函数实例 */
#include <iostream>
#include <cstdlib>
#include <ctime>

class ConstructedAndDestrcutor
{
private:
    int* array; // 指针,用来申请堆空间
    int arraySize; // 存储数组大小
public:
    ConstructedAndDestrcutor(); // 无参构造函数
    ConstructedAndDestrcutor(int arraySize_l, int maxNum_l); // 有参构造函数,初始化
    ~ConstructedAndDestrcutor(); // 析构函数,清理垃圾
    void initArray(int arraySize_l, int maxNum_l); // 初始化函数
    void displayData(); // 显示所有随机生成的数据
};

ConstructedAndDestrcutor::ConstructedAndDestrcutor()
{
    array = nullptr; // 初始化为nullptr
}

ConstructedAndDestrcutor::ConstructedAndDestrcutor(int arraySize_l, int maxNum_l)
{
    array = nullptr;
    initArray(arraySize_l, maxNum_l); // 调用初始化函数
}

ConstructedAndDestrcutor::~ConstructedAndDestrcutor()
{
    if (array != nullptr) // 如果array不是nullptr,即已经申请空间
    {
        delete[] array; // 释放array指向的堆内存
        array = nullptr;
        std::cout << "内存已释放!" << std::endl;
    }
    
}

void ConstructedAndDestrcutor::initArray(int arraySize_l, int maxNum_l)
{
    this->arraySize = arraySize_l; // 存储数组大小

    array = new int[arraySize_l]; // 申请堆空间

    srand((unsigned int)time(0)); // 用时间初始化随机数种子
    for (int i = 0; i < arraySize_l; i++)
    {
        array[i] = rand()%(maxNum_l + 1);
    }
    
}

void ConstructedAndDestrcutor::displayData()
{
    for (int i = 0; i < arraySize; i++)
    {
        std::cout << array[i] << std::ends;
    }
}

int main()
{

    int arraySize, maxNum;

    std::cout << "请输入随机生成的数字个数以及最大值,用空白符号或者英文逗号隔开:" << std::endl;
    std::cin >> arraySize >> maxNum;

    /* 这里用一对大括号表示对象的生命周期,因为vscode调用控制台会自动在主函数return时退出,而析构函数在return前很快完成,因此无法看到析构函数效果。这里将对象生命周期放到括号类,可以得到析构函数传达的信息。 */
    {
        ConstructedAndDestrcutor conades(arraySize, maxNum);
        conades.displayData();
    }

    return 0;

}

编译运行

>> 请输入随机生成的数字个数以及最大值,用空白符号或者英文逗号隔开:
<< 233 795
>> 656 562 125 259 710 264 350 437 152 756 726 22 755 129 404 790 479 513 638 265 180 288 342 116 226 224 794 518 63 261 637 542 693 554 610 395 401 341 499 203 679 708 299 92 627 564 415 486 604 642 179 196 183 471 487 270 582 220 703 19 589 175 16 85 400 737 140 694 410 81 326 552 210 583 274 291 353 728 593 284 607 453 604 725 177 358 773 264 766 442 563 590 184 691 579 509 477 567 182 568 219 84 253 709 245 492 687 211 59 466 710 569 232 15 485 666 377 196 684 547 325 479 100 494 443 441 429 176 776 59 261 617 114 647 554 490 108 573 163 368 286 513 519 723 499 351 695 682 481 330 749 37 217 221 416 642 56 404 170 25 165 317 608 773 396 438 285 40 262 594 685 478 526 770 15 724 115 526 693 372 331 404 675 103 412 248 2 220 641 332 651 395 775 223 175 568 317 340 72 93 188 488 437 785 415 40 634 479 390 428 779 394 180 289 550 718 167 107 315 131 383 71 179 364 440 274 478 591 732 406 657 523 774 内存已释放!

拷贝构造函数

构造函数 ,是一种特殊的方法。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。特别的一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们 即构造函数的重载

拷贝构造函数其实就是用对象实例化一个对象

源代码

/* CopyStructed.cpp 拷贝构造函数实例 */
#include <iostream>
#include <cstring>

class CopyStructed
{
private:
    char* name_l;
    int age;
public:
    CopyStructed(char* name_l, int age); // 构造函数
    CopyStructed(const CopyStructed& copstd); // 拷贝构造函数;const表示对象只读,不允许被修改
    ~CopyStructed(); // 析构函数
    void display();
};

CopyStructed::CopyStructed(char* name_l, int age)
{
    this->name_l = nullptr; // 指针置空

    /* 复制字符串姓名 */
    this->name_l = new char[strlen(name_l) + 1];
    strcpy(this->name_l, name_l);

    this->age = age; // 复制年龄
}

CopyStructed::CopyStructed(const CopyStructed& copstd)
{
    this->name_l = nullptr; // 指针置空

    /* 复制传入对象的字符串姓名 */
    this->name_l = new char[strlen(copstd.name_l) + 1]; // 保险起见,为'\0'额外申请一个空间
    strcpy(this->name_l, copstd.name_l); 

    this->age = copstd.age; // 复制传入对象的年龄
}

CopyStructed::~CopyStructed()
{
    if (this->name_l != nullptr)
    {
        std::cout << "正在清理" << this->name_l << std::endl;
        delete[] this->name_l; // name_l 不为空时释放空间
    }   
}

void CopyStructed::display()
{
    std::cout << "姓名:" << this->name_l << std::endl;
    std::cout << "年龄:" << this->age << std::endl;
}

int main()
{
    /* 这个大括号是为了让控制台显示析构函数(作用域) */
    {
        CopyStructed XiaoMing("小明", 28);
        XiaoMing.display();

        CopyStructed XiaoMing2(XiaoMing);
        XiaoMing2.display();
    }
    return 0;

}

编译运行

姓名:小明
年龄:28
姓名:小明
年龄:28
正在清理小明
正在清理小明

值得注意的是,拷贝堆空间的数据时,不能直接修改指针指向堆空间,而是用新对象的指针重新申请空间拷贝数据,否则因为我们需要在析构函数中设置清理空间的代码,原来对象的生命周期结束后,其指针指向的空间也会被释放,新对象的数据就会丢失

const和static

源代码

/* ConstAndStatic.cpp, const和static在类中的应用 */
#include <iostream>
#include <string>

class ConstAndStatic
{
private:
    static int sum_l; // 初始化一个sum_l来存储生成了多少对象
    std::string name;
public:
    ConstAndStatic(); // 默认构造函数
    ConstAndStatic(const std::string name); // 传入参数不允许被修改
    ~ConstAndStatic();
    std::string getName() const; // const成员函数无法修改类中成员和调用非const成员函数
    void display() const;
    static void classSum(); // 显示生成了多少个对象
};

int ConstAndStatic::sum_l = 0; // 必须在类外初始化类中的static成员,初始化时不需要加上static修饰

ConstAndStatic::ConstAndStatic()
{
    this->name = "默认姓名233";
    ConstAndStatic::sum_l++; // 生成对象数+1
}

ConstAndStatic::ConstAndStatic(const std::string name)
{
    this->name = name; // 传入const的姓名
    ConstAndStatic::sum_l++; // 生成对象数+1
}

ConstAndStatic::~ConstAndStatic()
{
    ConstAndStatic::sum_l--; // 生成对象数-1
}

std::string ConstAndStatic::getName() const
{
    return this->name; // 常常用来向类外复制一些数据。
}

void ConstAndStatic::display() const
{
    std::cout << "姓名:" << this->getName() << std::endl; // 非const成员函数可以调用const成员函数
}

void ConstAndStatic::classSum()
{
    std::cout << "当前有:" << ConstAndStatic::sum_l << "个对象被生成" << std::endl; // static成员可以直接在类外通过类名访问。它们独立于类,不与对象直接关联。
}

int main()
{

    ConstAndStatic::classSum(); // 显示当前该类实例化的对象数

    {
        ConstAndStatic castic("张三");
        ConstAndStatic::classSum(); // 显示当前该类实例化的对象数

        {
            ConstAndStatic castics[100]; // 实例化100个对象
            ConstAndStatic::classSum(); // 显示当前该类实例化的对象数
        }
        castic.display(); // 显示对象信息
        ConstAndStatic::classSum(); // 显示当前该类实例化的对象数
    }

    ConstAndStatic::classSum(); // 显示当前该类实例化的对象数
    
    return 0;

}

编译运行

当前有:0个对象被生成
当前有:1个对象被生成
当前有:101个对象被生成
姓名:张三
当前有:1个对象被生成
当前有:0个对象被生成

const用来保护类中的成员不被修改;

static则是可以不用创建对象也能调用的类中内容。

这是比较常见的用法,更多用处请自行探索~

内联函数

内联函数可以再一定程度上代替宏,效果是省去调用函数的开销,而是直接将函数生成到目标代码里。这是一种用空间换时间的方式。

使用方式很简单,在函数前加上inline标识即可。

内联函数不能有循环、开关等复杂语句,否则会被当成普通函数。

值得注意的是,类中成员函数通常会被默认定义为内联函数。

在类中声明同时定义的成员函数,自动转化为内联函数。
如果类中成员函数在类内部实现,并且没有循环、开关等复杂语句,就会默认当成内联函数;
如果类中成员在类外实现并且没有inline标识,则不会当成内联函数;
如果类中成员在类中声明时标识了inline,则会当成内联函数;
如果类中成员在类中声明未标识inline,但是类外实现,则会当成内联函数;
如果类中成员inline标识,但是实现在另外一个文件(.h和.cpp),则认为不是内联函数。

可以参考这一篇博客:C++类里面的哪些成员函数是内联函数?

友元

友元允许跨类访问私有成员。

先来友元函数

源代码

/* Friend.cpp 友元函数实例 */
#include <iostream>

/* 先定义两个类,因为后面涉及两个类互相调用的问题,先声明好就不会报错 */
class A;
class B;

class A
{
private:

public:
    A();
    ~A();
    void setNum(B& b, int num); // 用来修改B类的数据
};

A::A()
{
}

A::~A()
{
}

class B
{
private:
    int num; // 要被修改的数据
public:
    B();
    ~B();
    friend void display(B&); // 声明是普通函数的友元函数,用来显示私有信息
    friend void A::setNum(B& b, int num); // 声明是其他类中的友元函数,用来修改私有数据成员
};

B::B()
{
    num = 0;
}

B::~B()
{
}

void A::setNum(B& b, int num) // 函数实现,这个放在B类完整定义之后
{
    b.num = num;
}

void display(B& b) // 这是一个可以访问B类中私有成员的普通函数,也可以写成其他类的成员函数
{
    std::cout << b.num << std::endl;
}

int main()
{

    B b;
    display(b); // 显示b中数据
    A a;
    a.setNum(b, 2); // 修改b的数据
    display(b); // 显示b中数据
    return 0;

}

编译运行

0
2

接下来是友元类

源代码

/* FriendClass.cpp 友元类实例 */
#include <iostream>

class A; // 一样,先定义
class B;

class A
{
private:
    int num;
public:
    A();
    ~A();
    friend class B; // 定义B为友元类,B中的成员都可以访问A中所有数据。
};

A::A()
{
    this->num = 0; // 初始化为0
}

A::~A()
{
}

class B
{
private:
public:
    B();
    ~B();
    void changeNum(A&, int num); // 在B中修改A的数据
    void display(A&); // 在B中显示A的数据
};

B::B()
{
}

B::~B()
{
}

void B::changeNum(A& a, int num)
{
    a.num = num; // 更改值
}

void B::display(A& a)
{
    std::cout << a.num << std::endl; // 输出值
}

 int main()
 {
    
    A a; // 实例化A
    B b; // 实例化B
    b.display(a); // B显示A
    b.changeNum(a, 3); // B修改A的值为3
    b.display(a); // B显示A
     
    return 0;

 }

编译运行

0
3

更多特性请自行探索~


返回目录 1 面向对象技术
上一节 1.6 cpp的常见特性
下一节 1.8 继承


参考资料:

  • 《C++程序设计》传智播客
  • 博客园
  • CSDN
  • 百度百科

冰河相依
1 声望3 粉丝

于冰河相依。


下一篇 »
1.8 继承