C++编程-面向对象基础 (内联,引用,构造,析构,深拷贝与浅拷贝,静态,友元,类组合,常类型)

C++编程-面向对象基础

C++的面向对象编程,将所有的对象的共同特征以类的形式抽象出来,类的数据成员是对象的属性,方法成员(成员函数)即表现该属性的方法,创建一个类的对象即将该类实例化,而每个对象不一定有相同特征,会有差异于是有了类的继承来增添修改细节。

基本特征

抽象:所谓抽象是将特定的实例对象共同特征进行抽取形成概念的过程(注意一定要是对象)

封装:对数据进行权限控制隐藏,使外界无法得知数据细节

继承:简而言之就是实现相关的类细节差别,主干为基类,枝干为派生类

多态:对于不同的对象,有同名的行为,但是实现有区别,我们称之为多态

内联函数

在函数前以inline声明即为内联函数,其结果相当于将代码插入到了调用该函数的语句之中,同时以实参取代形参,并且不再进行函数调用,这种方式会增加内存占用,减少运行时间:

#include <iostream>
using namespace std;

inline void function();

int main(){

    function();
    return 0;
}

inline void function(){
    cout<<"Hello World"<<endl;
}

注意

  • 在第一次调用内联函数之前就应该将其定义完整,否则编译器将找不到要进行插入的代码
  • 内联函数内不能包含复杂的控制语句,如for/switch与递归操作
  • 只有简短语句和频繁调用的语句才使用内联,以此来提高运行速度
  • 在每个调用内联函数的地方,编译器都会在对应位置复制一份相同的汇编指令到内存

使用引用

引用相当于为已有的变量取了一个别名,引用是一种复合变量,它与const指针类似,引用永远指向原变量引用与原变量的地址是完全相同的(但是两者并不相同)

#include <iostream>
using namespace std;

int main(){
    int a = 4;      // 定义变量a 
    int &b = a;     // 定义a的引用
    int *p = &a;    // 使用指向a的指针

    cout<<&p<<endl;  // 输出指针地址
    cout<<&b<<endl;  // 输出引用的地址
    cout<<&a<<endl;  // 输出a的地址 

    return 0;
}

注意

  • 引用可任意使用合法的变量名,除了返回引用类型的函数外,每个引用必须全部以已存在变量初始化
  • 引用是通过别名直接访问某个变量,而指针是通过地址间接访问变量(引用不是数据类型,但是引用是一种复合类型)
  • 每个引用只效忠于其初始化的变量,不能够改变引用的指向
  • 不允许建立void类型的引用,不允许建立引用的引用不允许建立引用类型的指针
  • 可以将引用的地址赋给指针,因为引用的地址与原数据相同

类的声明

C++的类由C语言的结构体改良而来,增加了权限与函数成员,并且和结构体一样,未声明对象不会为其分配内存。下面是类的声明:

  • 仅声明,而不定义其内容
class CLASS_NAME;   // 声明了一个叫做CLASS_NAME的类
  • 声明,并定义内容
class CLASS_NAME{	// 声明并定义了一个叫做CLASS_NAME的类
    
    // code...
    
}; // 注意分号

权限控制

访问类型

  • 类内访问:指的是通过类内部的函数进行访问
#include <iostream>

class CLASS_NAME {

    public:
        int data;
        void print(){ std::cout<<data<<std::endl; } // 虽然是公有成员 但仍然可以内部访问
    
};
  • 类外访问:通过类的对象与指针进行访问
#include <iostream>

class CLASS_NAME {

    public:
        int data;
        void print(){ std::cout<<data<<std::endl; }
    
};

int main(){

    CLASS_NAME object;
    object.data = 123;  // 使用对象在类外访问

    CLASS_NAME *p = new CLASS_NAME;

    p -> data = 456;  // 使用对象指针访问

    object.print();
    p -> print();

    return 0;
}

类的权限

C++的类有三种权限,分别是:公有私有保护。并且在一般情况下类中定义的成员一般是内部链接性的,仅供当前翻译单元使用

  • 公有权限

在类中使用 public进行声明,表明该成员可以被类外访问,如上面访问类型的示例代码

  • 私有权限

在类中使用 private进行声明,表明该成员只能被类内访问并且类中未声明权限部分默认为私有权限

#include <iostream>

class CLASS_NAME {

        int data;
    public:
        void print(){ std::cout<<data<<std::endl; }
    
};

int main(){

    CLASS_NAME object;
    // object.data = 123;  // 访问失败 不允许访问该私有成员

    CLASS_NAME *p = new CLASS_NAME;

    // p -> data = 456;  // 访问失败 不允许访问该私有成员


    object.print(); // 通过调用函数访问 即类内访问 并且输出无效值 因为data内存未初始化或定义
    p -> print();

    return 0;
}
  • 保护权限

在类中使用 protected进行声明,此权限表明在基类中的访问仍然是内部访问,并且在继承过后这类成员将作为派生类的保护成员(非私有继承)

#include <iostream>

class CLASS_NAME {
    protected:
        int data;
    public:
        void print(){ std::cout<<data<<std::endl; }
    
};

class CLASS_ONE : public CLASS_NAME{

};


int main(){

    CLASS_ONE object;
    
    object.print();

    return 0;
}

成员函数

外部定义成员函数(推荐写法)

//
// CLASS_NAME.h
//

#ifndef UNTITLED2_CLASS_NAME_H
#define UNTITLED2_CLASS_NAME_H
#include <iostream>


class CLASS_NAME {
    int data;
public:
    void print();
    void setData(int x);

};

#endif //UNTITLED2_CLASS_NAME_H

//
// CLASS_NAME.cpp
//

#include "CLASS_NAME.h"

void CLASS_NAME::print() { std::cout<<data<<std::endl; }
void CLASS_NAME::setData(int x) { data = x; }
//
// main.cpp
//
#include <iostream>
#include"CLASS_NAME.h"

int main() {

    CLASS_NAME object;

    object.setData(1234);
    object.print();

    return 0;
}

如上述代码,我们在类外定义具有外部链接性的成员函数使用如下的格式在类外定义:

returnType className::functionName(dataList)

在类的定义中使用函数的原型声明

注意:我们在外部若有同名函数在当前翻译单元中,使用 ::在函数前方访问的将是全局名称空间中的同名函数,例如:::functionName(dataList)

内联函数定义(不推荐)

//
// CLASS_NAME.h
//

#ifndef UNTITLED2_CLASS_NAME_H
#define UNTITLED2_CLASS_NAME_H
#include <iostream>


class CLASS_NAME {
    int data;
public:
    inline void print();
    inline void setData(int x);

};

inline void CLASS_NAME::print() { std::cout<<data<<std::endl; }
inline void CLASS_NAME::setData(int x) { data = x; }

#endif //UNTITLED2_CLASS_NAME_H
//
// main.cpp
//
#include <iostream>
#include"CLASS_NAME.h"

int main() {

    CLASS_NAME object;

    object.setData(1234);
    object.print();

    return 0;
}

在这种定义下,我们使用无链接性的内联函数定义成员函数,并且使用以下格式:

声明:inline returnType functionName(dataList)

定义:inline returnType className::functionName(dataList)

并不是很推荐这种定义方式,但是其运行速度要更快,占用内存较大,不适合长代码块内联,并且为了规避二义性,所有内联函数在使用时不能够存在不同定义,尤其是多翻译单元处理,这种方式并不符合文件组织的原则,并且无法有效利用C++单独编译的特性,尤其不推荐,该方式直接将定义写在类中,在多文件中将会有可能引入多重定义,直接导致项目崩溃,并且可维护性大大降低

定义在类的内部(不推荐)

这种方式实际上也是将成员函数作为内联函数处理,这种方式并不符合文件组织的原则,并且无法有效利用C++单独编译的特性,也不推荐,该方式直接将定义写在类中,在多文件中将会有可能引入多重定义,直接导致项目崩溃,并且可维护性大大降低,慎用

//
// CLASS_NAME.h
//

#ifndef UNTITLED2_CLASS_NAME_H
#define UNTITLED2_CLASS_NAME_H
#include <iostream>


class CLASS_NAME {
    int data;
public:
    void print(){ std::cout<<data<<std::endl; }
    void setData(int x){ data = x; }

};


#endif //UNTITLED2_CLASS_NAME_H

//
// main.cpp
//
#include <iostream>
#include"CLASS_NAME.h"

int main() {

    CLASS_NAME object;

    object.setData(1234);
    object.print();

    return 0;
}

构造与析构

构造函数

很多时候我们需要对类的对象在创建时进行初始化,我们会采取构造函数对其进行初始化,并且构造函数通常是公有的,如果C++类的构造函数被声明为私有,那么它不能从类的外部直接调用。这意味着只有在该类内部才能创建对象。通常情况下,这种做法被称为单例设计模式,用于确保只有一个实例可以被创建,并且该实例可以全局访问。如果试图从类的外部调用私有构造函数,则会收到编译错误。

普通构造函数

定义构造函数

//
// CLASS_NAME.h
//

#ifndef UNTITLED2_CLASS_NAME_H
#define UNTITLED2_CLASS_NAME_H
#include <iostream>

class CLASS_NAME {
    int data;
public:
    CLASS_NAME(int data_); // 定义构造函数 无返回值 名称同类名一致
    void print();
    void setData(int x);

};

#endif //UNTITLED2_CLASS_NAME_H
//
// CLASS_NAME.cpp
//

#include "CLASS_NAME.h"

CLASS_NAME::CLASS_NAME(int data_) { data = data_; }
void CLASS_NAME::print() { std::cout<<data<<std::endl; }
void CLASS_NAME::setData(int x) { data = x; }
//
// main.cpp
//
#include <iostream>
#include"CLASS_NAME.h"

int main() {

    CLASS_NAME object(233);
    object.print();

    object.setData(1234);
    object.print();

    object.setData(123);
    object.print();

    return 0;
}

运行结果

在这里插入图片描述

构造函数格式

声明:className(dataList);

定义:className::className(dataList)

注意

  1. 构造函数是由系统自动调用的(创建一个对象,调用一次),而初始化函数setData需要我们手动调用,并且可以调用多次
  2. 在将构造函数直接定义在类中,系统也是默认将其作为内联函数处理
  3. 在未定义构造函数时,系统会默认给每个类生成一个默认构造函数,它不带任何参数与函数体,也不能进行数据成员赋值
构造函数的成员初始化列表赋值

这是一种特殊的初始化方法,我们只需修改下面给出示例:

//
// CLASS_NAME.cpp
//
#include "CLASS_NAME.h"

CLASS_NAME::CLASS_NAME(int data_):data(data_){ }
void CLASS_NAME::print() { std::cout<<data<<std::endl; }
void CLASS_NAME::setData(int x) { data = x; }

运行出来效果是一样的

在这里插入图片描述

由于声明格式无变化,下给出定义格式:

className::className(dataList):class's realData(imageData),class's realData(imageData)...

后方的参数随定义数量而变

使用构造函数初始化列表的情况

  1. 常量成员变量:如果类中有常量成员变量,它们必须在对象创建时进行初始化,并且不能在构造函数体内赋值。这时候就需要使用构造函数初始化列表。
  2. 引用类型成员变量:引用类型成员变量必须在对象创建时初始化,因为引用不能重新绑定到另一个对象。因此,使用构造函数初始化列表可以直接将引用参数传递给构造函数。
  3. 类型转换或初始化顺序的问题:如果类中有多个成员变量,它们之间存在某些依赖关系,使用构造函数初始化列表可以确保正确的初始化顺序和类型转换。
构造函数的变化

对于其本质来讲,构造函数始终是函数,那么它也支持普通函数的重载,默认值,参数引用等等,下面给出示例:

//
// CLASS_NAME.h
//

#ifndef UNTITLED2_CLASS_NAME_H
#define UNTITLED2_CLASS_NAME_H
#include <iostream>

class CLASS_NAME {
    int data1;
    int data2;
    int data3;
public:
    CLASS_NAME();
    explicit CLASS_NAME(const int &data1_,int data2_=123);
    CLASS_NAME(const int &data1_,const int &data2_,const int &data3_);

};

#endif //UNTITLED2_CLASS_NAME_H
//
// CLASS_NAME.cpp
//

#include "CLASS_NAME.h"

CLASS_NAME::CLASS_NAME(){
    std::cout<<"Not value construction"<<std::endl;
}

CLASS_NAME::CLASS_NAME(const int &data1_,int data2_){   // 声明写了默认值 定义时就不用了
    data1 = data1_;
    data2 = data2_;
    std::cout<<"Two value construction"<<std::endl;
    std::cout<<data1<<" "<<data2<<std::endl<<"data3 is invalid value"<<std::endl;
}

CLASS_NAME::CLASS_NAME(const int &data1_,const int &data2_,const int &data3_):data1(data1_),data2(data2_),data3(data3_){
    std::cout<<"Three value construction"<<std::endl;
    std::cout<<data1<<" "<<data2<<" "<<data3<<std::endl;
}
//
// main.cpp
//
#include"CLASS_NAME.h"

int main() {

    CLASS_NAME object1; // 无参构造时 无需加括号
    std::cout<<"\n";
    CLASS_NAME object2(233);
    std::cout<<"\n";
    CLASS_NAME object3(233,233,233);

    return 0;
}

我们定义了三个构造函数,一个无参,一个带两个参数(并且有一个默认值),一个带三个参数,运行后结果:

在这里插入图片描述

注意

  1. 在定义了无参构造函数未定义构造函数时,不能写成className objectName()的原因是:使用这种语句将会声明一个返回该对象的一个普通函数
  2. 只要定义了构造函数,哪怕是无参的,系统都将不会提供默认的构造函数
  3. 尽管定义了多个构造函数,但是创建对象时,系统只会根据情况选择一个函数进行调用
  4. 在类中定义了所有参数都有默认值时,将不能够重载构造函数
拷贝构造函数

对象赋值,对象作为参数传递,对象作为返回值并被接收,使用对象为对象初始化调用的是拷贝构造函数,拷贝构造函数用于将一个对象赋值给另一个对象,使两对象的数据完全一致

//
// CLASS_NAME.h
//

#ifndef UNTITLED2_CLASS_NAME_H
#define UNTITLED2_CLASS_NAME_H
#include <iostream>

class CLASS_NAME {
    std::string data;
public:
    CLASS_NAME(std::string data_);
    CLASS_NAME(const CLASS_NAME &tempObject);
};

#endif //UNTITLED2_CLASS_NAME_H
//
// CLASS_NAME.cpp
//

#include "CLASS_NAME.h"

CLASS_NAME::CLASS_NAME(std::string data_){
    data = data_;
}
CLASS_NAME::CLASS_NAME(const CLASS_NAME &temp) {
    data = temp.data;
    std::cout<<"The object is copied , data value is "<<data<<std::endl;
}
//
// main.cpp
//
#include"CLASS_NAME.h"

CLASS_NAME function(CLASS_NAME object){
    return object;
}

int main() {

    CLASS_NAME object("TestOne");
    std::cout<<"Main object is constructed"<<std::endl;
    CLASS_NAME object1 = object;  // 用等于号赋值 复制
    CLASS_NAME object2(object);  // 调用拷贝构造函数 复制

    std::cout<<std::endl;

    std::cout<<"The function is going to run"<<std::endl;
    CLASS_NAME object3 = function(object);

    return 0;
}

运行结果

在这里插入图片描述

这里需要注意的是,调用拷贝构造函数一定是在构建一个对象时,对两个已有对象进行赋值操作实际上是赋值运算符的功能,并且最后一条输出一定是在有对象接收该函数的返回值时,才会有赋值构造行为。

注意

  1. 拷贝构造函数较为特殊,在只定义了拷贝构造函数时,系统仍然会提供默认的普通构造函数
  2. 拷贝构造函数只能适用于本类的实例化对象,并且它只有一个参数,是该类对象的常引用
  3. 在未定义拷贝构造函数时,系统也会提供一个只会进行赋值操作的拷贝构造函数

析构函数

析构函数与构造函数的功能刚好相反,用于清理释放分配给对象的内存

析构函数格式~className()

注意

  1. 析构函数不返回任何值,没有返回类型。析构函数无参数,不能重载,一个类只能有一个析构函数
  2. 在未定义析构函数时,系统会自动生成一个析构函数,并且析构函数会自动调用
  3. 在函数体内定义的临时对象将会在函数结束时调用析构函数,使用对象指针创建的对象在delete时也会调用析构函数
//
// CLASS_NAME.h
//

#ifndef UNTITLED2_CLASS_NAME_H
#define UNTITLED2_CLASS_NAME_H
#include <iostream>

class CLASS_NAME {
public:
    CLASS_NAME();
    ~CLASS_NAME();
};

#endif //UNTITLED2_CLASS_NAME_H
//
// CLASS_NAME.cpp
//

#include "CLASS_NAME.h"

CLASS_NAME::CLASS_NAME(){
    std::cout<<"The object is constructed"<<std::endl;
}
CLASS_NAME::~CLASS_NAME() {
    std::cout<<"The object is destroyed"<<std::endl;
}
//
// main.cpp
//
#include"CLASS_NAME.h"

void function(){
    CLASS_NAME object;
}

int main() {

    function();
    std::cout<<"End of function"<<std::endl;
    std::cout<<"---------------------------"<<std::endl;

    CLASS_NAME object;

    std::cout<<"---------------------------"<<std::endl;

    CLASS_NAME *p = new CLASS_NAME;
    delete p;

    return 0;
}

运行结果

在这里插入图片描述

可以看到函数代码块结束后,其定义的类对象也自动析构,而定义在同一代码块中的所有对象将会在最后一起释放,如main函数中的(指针创建的对象未被delete时)

深拷贝与浅拷贝

浅拷贝

浅拷贝指的是仅复制并且赋值的拷贝方式,通常情况下不会出现问题,但是当类的成员出现指针时会有以下情况(由于系统默认的拷贝构造函数只会复制值,我们依此来演示):

//
// CLASS_NAME.h
//

#ifndef UNTITLED2_CLASS_NAME_H
#define UNTITLED2_CLASS_NAME_H
#include <iostream>

class CLASS_NAME {
    std::string *data;
public:
    CLASS_NAME(const std::string &data_);
    ~CLASS_NAME();

};

#endif //UNTITLED2_CLASS_NAME_H
//
// CLASS_NAME.cpp
//

#include "CLASS_NAME.h"

CLASS_NAME::CLASS_NAME(const std::string &data_){
    data = new std::string(data_);
}
CLASS_NAME::~CLASS_NAME(){
    std::cout<<"This object is destroyed"<<std::endl;
    delete data;  // 定义析构函数释放动态存储持续性的指针成员
}
//
// main.cpp
//
#include"CLASS_NAME.h"

int main() {

    CLASS_NAME object("TestOne");
    std::cout<<"Main object is constructed"<<std::endl<<std::endl;
    CLASS_NAME object1 = object;
    return 0;
}

运行结果

在这里插入图片描述

我们运行后发现IDE丢出错误,发生了内存越界,原因是浅拷贝只会将相同的地址赋值给对象成员,导致在析构时同一个地址被释放两次发生指针悬挂,我们可以输出地址进行验证,修改CLASS_NAME.cpp:

//
// CLASS_NAME.cpp
//

#include "CLASS_NAME.h"

CLASS_NAME::CLASS_NAME(const std::string &data_){
    data = new std::string(data_);
}
CLASS_NAME::~CLASS_NAME(){
    std::cout<<"This object is destroyed"<<std::endl;
    std::cout<<data<<std::endl;
    delete data;  // 定义析构函数释放动态存储持续性的指针成员
}

得到运行结果

在这里插入图片描述

可见同一地址被释放了两次,而且这种情况可能发生在不经意间,尤其是传值调用时,如下修改后代码:

//
// main.cpp
//
#include"CLASS_NAME.h"

int main() {

    // 注意这里使用了强制转换
    CLASS_NAME object[3] = {std::string("test"),std::string("test"),std::string("test")};
    std::cout<<"Main object is constructed"<<std::endl<<std::endl;
    for(auto i:object){
        i.print();
    }

    return 0;
}

运行结果

在这里插入图片描述

发生内存越界并且输出和预期存在差异,原因是因为以此结构的循环遍历会将每个对象赋值给i这个临时对象,而临时对象被析构会释放指针的内存,而原本对象析构又会释放一次,解决方法是使用引用,防止临时对象的产生:

//
// main.cpp
//
#include"CLASS_NAME.h"

int main() {

    // 注意这里使用了强制转换
    CLASS_NAME object[3] = {std::string("test"),std::string("test"),std::string("test")};
    std::cout<<"Main object is constructed"<<std::endl<<std::endl;
    for(auto &i:object){
        i.print();
    }

    return 0;
}

运行结果

在这里插入图片描述

注意:在此代码中,如果只定义单个对象,即使在构造函数中使用C风格字符串,也不会出现问题。这是因为单个对象的内存是由操作系统分配的,在构造函数中使用C风格字符串时,编译器会自动将它转换为std::string类型,并为对象分配足够的内存来容纳该字符串。而当使用对象数组时,每个对象的内存都是紧密相连的,如果不为每个对象分配足够的内存来容纳字符串,则会导致指针指向未知的内存区域,从而导致不可预测的行为。

深拷贝

对于上述代码中的问题,其实就是指针的问题,那么我们只需要自定义拷贝构造函数,将指针需要的内存合理分配即可解决这个问题,这种方式我们称之为深拷贝:

//
// CLASS_NAME.h
//

#ifndef UNTITLED2_CLASS_NAME_H
#define UNTITLED2_CLASS_NAME_H
#include <iostream>

class CLASS_NAME {
    std::string *data;
public:
    CLASS_NAME(const std::string &data_);
    CLASS_NAME(const CLASS_NAME &temp);
    ~CLASS_NAME();
    void print();

};

#endif //UNTITLED2_CLASS_NAME_H
//
// CLASS_NAME.cpp
//

#include "CLASS_NAME.h"

CLASS_NAME::CLASS_NAME(const std::string &data_): data(new std::string(data_)){}

CLASS_NAME::~CLASS_NAME(){
    std::cout<<"This object is destroyed"<<std::endl;
    std::cout<<data<<std::endl;
    delete data;  // 定义析构函数释放动态存储持续性的指针成员
}

CLASS_NAME::CLASS_NAME(const CLASS_NAME &temp){
    data = new std::string(*temp.data);

}

void CLASS_NAME::print() {
    std::cout<<*data<<"  ";
}
//
// main.cpp
//
#include"CLASS_NAME.h"

int main() {

    // 注意这里使用了强制转换
    CLASS_NAME object[3] = {std::string("test"),std::string("test"),std::string("test")};
    std::cout<<"Main object is constructed"<<std::endl<<std::endl;
    for(auto i:object){
        i.print();
    }
    std::cout<<"----------------------"<<std::endl;

    return 0;
}

得到下面的正常运行结果

在这里插入图片描述

This指针

C++中使用this指针来追踪不同的对象,只有当调用一个对象时,this指针才会指向该对象。要使用整个对象可以用*this

//
// CLASS_NAME.h
//

#ifndef UNTITLED2_CLASS_NAME_H
#define UNTITLED2_CLASS_NAME_H
#include <iostream>

class CLASS_NAME {
public:
    CLASS_NAME();
    ~CLASS_NAME();
};

#endif //UNTITLED2_CLASS_NAME_H
//
// CLASS_NAME.cpp
//

#include "CLASS_NAME.h"

CLASS_NAME::CLASS_NAME(){
    std::cout<<"This pointer is "<<this<<std::endl;
}
CLASS_NAME::~CLASS_NAME() {
    std::cout<<"The object is destroyed"<<std::endl;
}
//
// main.cpp
//
#include"CLASS_NAME.h"

int main() {
    CLASS_NAME object;
    return 0;
}

如上述代码中,我们使用this指针输出对象的地址,输出对象的数据成员可以使用 this->dataMember

静态数据成员

在类中想要让所有对象使用同一个数据,我们可以使用全局变量,但是为了符合面向对象的原则,我们更推荐使用类的静态成员,同时类的静态成员是静态存储持续性变量,链接性是内部的。

声明格式static dataType dataName

定义(类外初始化)格式dataType className::dataName = value

访问方式className::dataName

//
// TEAS.h.
//
#ifndef UNTITLED3_TEST_H
#define UNTITLED3_TEST_H

class TEST {
public:
    static int data;
};

#endif //UNTITLED3_TEST_H
//
// TEST.cpp
//
#include "TEST.h"

int TEST::data = 233;
//
//  main.cpp
//
#include "TEST.h"
#include <iostream>

int main() {
    TEST object;
    std::cout << object.data << std::endl << std::endl;

    TEST *pObject = new TEST;
    std::cout << pObject->data << std::endl << std::endl;

    std::cout << TEST::data << std::endl << std::endl;

    return 0;
}

运行结果

在这里插入图片描述

上述代码我们使用了三种不同的访问方式(注意静态成员的权限是公有的)访问静态成员验证了它是被所有对象共享的,它是属于这个类的

静态成员函数

声明格式static returnType functionName(dataList)

定义格式returnType className::functionName(dataList)

并且同样有以下三种访问方式

//
// TEAS.h.
//
#ifndef UNTITLED3_TEST_H
#define UNTITLED3_TEST_H

class TEST {
    static int data;
public:
    static void function();
};

#endif //UNTITLED3_TEST_H
//
// TEST.cpp
//
#include "TEST.h"
#include <iostream>

int TEST::data = 233;
void TEST::function() {
    std::cout<<data<<std::endl;
}
//
//  main.cpp
//
#include "TEST.h"
#include <iostream>

int main() {
    TEST object;
    object.function();

    TEST *pObject = new TEST;
    pObject->function();

    TEST::function();

    return 0;
}

注意

  1. 对于静态成员函数与静态数据成员都是没有this指针的,因为它们属于类,供所有对象共享
  2. 静态成员函数与静态数据成员可以在未创建任何对象前通过className::的方式进行访问(前提是它们的权限是公有的,否则只能在类的成员函数中访问)
  3. 普通静态成员函数只能访问静态数据成员,如果要访问对象的普通成员,必须要通过以下方式
//
// TEAS.h.
//
#ifndef UNTITLED3_TEST_H
#define UNTITLED3_TEST_H
#include <iostream>

class TEST {
    int data1;
    static int data;
public:
    static void function(TEST &temp);
};

#endif //UNTITLED3_TEST_H
//
// TEST.cpp
//
#include "TEST.h"

int TEST::data = 233;

void TEST::function(TEST &temp) {
    temp.data1 = 233;
    std::cout << data <<"  "<< temp.data1 << std::endl;
}
//
//  main.cpp
//
#include "TEST.h"

int main() {
    TEST object;
    TEST::function(object);
    return 0;
}

如上代码我们定义了一个带有该类对象引用的静态成员函数,在该静态成员函数中我们使用对象进行访问,并且这种方式仍然属于内部访问(可以理解为该引用为静态成员函数指明了应该访问的对象是谁)

友元函数

非成员友元函数

声明friend returnType functionName(className &)

定义returnType functionName(className &name)

//
// TEAS.h.
//
#ifndef UNTITLED3_TEST_H
#define UNTITLED3_TEST_H
#include <iostream>

class TEST {
    int data;
public:
    TEST(int x);
    friend void fun(TEST &);
};

#endif //UNTITLED3_TEST_H
//
// TEST.cpp
//
#include "TEST.h"

TEST::TEST(int x) { data = x; }

void fun(TEST &temp){
    std::cout<<temp.data<<std::endl;
}
//
//  main.cpp
//
#include "TEST.h"

int main() {
    TEST object(123);
    fun(object);
    return 0;
}

上述代码中定义了一个非成员的友元函数,并且使用它输出了类中的私有成员 data

定义多个类的友元函数

只需要在友元函数的参数中增加另外类对象的引用,并且用const修饰即可,:

声明friend returType functionName(className & , .... )

定义returType functionName(className &objectName , .... )

注意

  1. 友元函数并不是类的成员,所以它不能直接访问类的私有成员,并且它也不存在this指针
  2. 友元函数中的const是可选的,是否使用取决于我们是否实现对其成员的修改
  3. 友元函数并不会使安全性与可维护性大大降低,虽然其仍然会有影响

成员友元函数

这种声明方式是将一个类中的成员函数作为另一个类的友元函数,使这个类有访问另一个类的权限,该函数在自身类中的定义与原本一致,而在其他类中则稍有区别:

在其他类中声明friend returType classONE::functionName(classOther & , .... )

定义returnType classOne::functionName(className &name)

即加上作用域符号表明作用域即可,下面给出示例代码:

//
// TEAS.h.
//
#ifndef UNTITLED3_TEST_H
#define UNTITLED3_TEST_H
#include <iostream>
class TEST_TWO;

class TEST_ONE {
    int data;
public:
    explicit TEST_ONE(int x);
    void function(TEST_TWO &);
};

class TEST_TWO {
    int data;
public:
    explicit TEST_TWO(int x);
    friend void TEST_ONE::function(TEST_TWO &);
};

#endif //UNTITLED3_TEST_H
//
// TEST.cpp
//
#include "TEST.h"

TEST_ONE::TEST_ONE(int x) { data = x; }

void TEST_ONE::function(TEST_TWO &temp){
    temp.data = 233;
    std::cout<<temp.data<<std::endl;
}

// 第二个类
TEST_TWO::TEST_TWO(int x) { data = x; }
//
//  main.cpp
//
#include "TEST.h"

int main() {
    TEST_ONE object(123);
    TEST_TWO object1(111);
    object.function(object1);
    return 0;
}

友元类

当在A类中使用:friend className对另一个类B进行声明时,那么B类将成为A类的友元类。此时B类中的所有成员函数都将成为A类的友元函数(当然要访问B类的话还是要进行对象引用),下面给出示例:

//
// TEAS.h.
//
#ifndef UNTITLED3_TEST_H
#define UNTITLED3_TEST_H
#include <iostream>
class TEST_TWO;

class TEST_ONE {
public:
    void setData(TEST_TWO &);
    void function(TEST_TWO &);
};

class TEST_TWO {
    int data;
public:
    explicit TEST_TWO(int x);
    void print();
    friend TEST_ONE;
};

#endif //UNTITLED3_TEST_H
//
// TEST.cpp
//
#include "TEST.h"

void TEST_ONE::setData(TEST_TWO &temp){
    temp.data = 233;
    std::cout<<"Now TEST_ONE's setData is running"<<std::endl;
    std::cout<<"Now data's value is changed"<<std::endl;
}

void TEST_ONE::function(TEST_TWO &temp){
    std::cout<<"Now TEST_ONE's function is running"<<std::endl;
    std::cout<<"Now data's value is "<<temp.data<<std::endl;
}

// 第二个类
TEST_TWO::TEST_TWO(int x) { data = x; }
void TEST_TWO::print() {
    std::cout<<"Now TEST_TWO's print is running"<<std::endl;
    std::cout<<"Now data's value is "<<data<<std::endl;
}
//
//  main.cpp
//
#include "TEST.h"

int main() {
    TEST_ONE object;
    TEST_TWO object1(111);
    object1.print();

    std::cout<<"---------------"<<std::endl;

    object.setData(object1);
    object.function(object1);

    std::cout<<"---------------"<<std::endl;
    object1.print();
    return 0;
}

通过上述代码我们可以进行验证,有以下运行结果:

在这里插入图片描述

类的组合

类的成员也可以是其他类的对象,但是不能够是自身类的对象可以是自身类对象指针

//
// TEST.h
//

#ifndef UNTITLED1_TEST_H
#define UNTITLED1_TEST_H
#include<iostream>
#include<string>

class A{
public:
    std::string a;
    explicit A(std::string a_);
};

class B{
public:
    std::string b;
    explicit B(std::string b_);
};

class TEST {
public:
    A objectOne;
    B objectTwo;
    TEST(std::string a_,std::string b_);
};

#endif //UNTITLED1_TEST_H
//
// TEST.cpp
//
#include "TEST.h"

TEST::TEST(std::string a_,std::string b_): objectOne(a_), objectTwo(b_){
    std::cout<<"The TEST object has been created"<<std::endl;
}
A::A(std::string a_){
    std::cout<<"The A object has been created"<<std::endl;
}
B::B(std::string b_){
    std::cout<<"The B object has been created"<<std::endl;
}
#include "TEST.h"

int main() {

    TEST object("Hello","World");

    return 0;
}

运行上述代码

在这里插入图片描述

当我们交换A,B对象的定义顺序可以得到以下结果:

在这里插入图片描述

  • 可以发现在有其他类对象成员时,构造顺序是按定义类对象的顺序开始依次构造最后才是自身类的构造
  • 对于有类成员的类的构造,我们通常使用成员初始化列表来进行构造

常类型

常对象与常数据成员

在对创建的类对象使用const修饰,即说明了对象为常对象,常对象中的数据成员不允许被改变

定义方式const className objectName (dataList)

并且我们可以在类中定义常数据成员,这类成员也不允许被成员函数修改,所以在进行构造时只能通过成员初始化列表进行构造,在函数体中对其进行初始化是错误的

常成员函数

声明定义格式returnType functionName(dataList) const

注意

  • 常成员函数的const表明该函数对数据是只读的,它属于一种数据限定格式,并且允许以此重载
  • 常成员函数可以访问常数据成员,也可以访问普通数据成员
  • 对于普通对象的常数据成员,普通成员函数可以访问它,但是不能改变他的值,而常对象的常数据成员,普通成员函数不能访问和改变其数据,而常成员函数对于所有的数据成员都仅仅只是可读的
  • 如果一个对象被声明为常对象,那么通过该对象只能够调用其常成员函数
  • 常成员函数只能够访问,不能修改数据,也不能调用该类中的普通成员函数
//
// CLASS_NAME.h
//

#ifndef UNTITLED2_CLASS_NAME_H
#define UNTITLED2_CLASS_NAME_H
#include <iostream>
#include <string>

class CLASS_NAME {
    std::string name;
    std::string action;
public:
    CLASS_NAME(std::string name_,std::string action_);
    void print()const;

};

#endif //UNTITLED2_CLASS_NAME_H
//
// CLASS_NAME.cpp
//
#include "CLASS_NAME.h"

CLASS_NAME::CLASS_NAME(std::string name_, std::string action_) {
    name = name_;
    action = action_;
}

void CLASS_NAME::print() const {
    std::cout<<"I'm "<<name<<",I like "<<action<<std::endl;
}
//
// main.cpp
//
#include "CLASS_NAME.h"

int main() {
    CLASS_NAME object("Chicken","sing,dance,rap,basketball");

    object.print();

    return 0;
}

参考文献

C++面向对象程序设计教程(陈维兴)
C++ Primer Plus
C++官方文档
菜鸟教程
并且以C++ Primer Plus,C++官方文档为标准对陈教材中不规范代码进行了修改,同时对教材中的单文件代码进行修改,使之更符合生产标准

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值