创建一个新的类

在不同的文件中定义新的类通常是一个好习惯。这使得维护和阅读代码更容易。

为此,请在 CodeBlocks 中使用以下步骤:

点击 File->New->Class...

给你的新类一个名字,取消选中 “Has destructor”(具有析构函数),勾选 "Header and implementation file shall be in same folder"(头文件和实现文件应该在同一个文件夹中),然后点击 "Create"(创建) 按钮。

请注意,两个新文件已添加到您的项目中:

新类模板。

  • MyClass.h 是头文件。

  • MyClass.cpp 是源文件。

源文件和头文件
头文件(.h)包含函数声明(原型)和变量声明。

它目前包括一个我们新的 MyClass 类的模板,带有一个默认的构造函数。

MyClass.h

#ifndef MYCLASS_H
#define MYCLASS_H

class MyClass
{
  public:
    MyClass();
  protected:
  private:
};

#endif // MYCLASS_H

类的实现及其方法在源文件(.cpp)。

目前它只包含一个空的构造函数。

MyClass.cpp

#include "MyClass.h"

MyClass::MyClass()
{
   //ctor
}

范围解析运算符

源文件(.cpp)中的双冒号称为作用域解析运算符,用于构造函数定义:

#include "MyClass.h"

MyClass::MyClass()
{
   //ctor
}

范围解析运算符用于定义已经声明的特定类的成员函数。 请记住,我们在头文件中定义了构造函数原型。

所以,MyClass::MyClass() 引用 MyClass() 成员函数,或者在这种情况下 MyClass 类的构造函数。

要在 main 中使用我们的类,我们需要包含我们的头文件。

例如,要在 main 中使用我们新创建的 MyClass:

#include <iostream>
#include "MyClass.h"
using namespace std;

int main() {
  MyClass obj;
}

头文件声明了一个类将做什么,而 cpp 源文件定义了它将如何执行这些功能。

析构函数

析构函数的名字将与该类完全相同,只是有前缀(~)。析构函数不能返回值或取任何参数。

class MyClass {
  public: 
    ~MyClass() {
     // some code
    }
};

析构函数在退出程序之前可以非常有用地释放资源。这可以包括关闭文件,释放内存等等。

例如,我们在头文件 MyClass.h 中为我们的 MyClass 类声明一个析构函数:

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

在头文件中声明析构函数后,我们可以在源文件 MyClass.cpp 中编写实现:

#include "MyClass.h"
#include <iostream>
using namespace std;

MyClass::MyClass()
{
  cout<<"Constructor"<<endl;
}

MyClass::~MyClass()
{
  cout<<"Destructor"<<endl;
}

请注意,我们包含 iostream,以便我们可以使用 cout。
由于析构函数不能带参数,所以也不能重载。每个类将只有一个析构函数。

定义析构函数不是强制性的。如果你不需要,你可以不定义。
让我们回到主函数来

#include <iostream>
#include "MyClass.h"
using namespace std;

int main() {
  MyClass obj;

  return 0;
}

我们包含了类的头文件,然后创建了这种类型的对象。

这将返回以下输出:

Constructor
Destructor
程序运行时,首先创建对象并调用构造函数。当程序执行完成时,对象被删除,析构函数被调用。

请记住,我们在构造函数中打印 “Constructor”,在析构函数中打印 “Destructor”。

#ifndef & #define

我们为我们的类创建了单独的头文件和源文件,生成的头文件内容如下。

#ifndef MYCLASS_H
#define MYCLASS_H

class MyClass
{
  public:
  MyClass();
  protected:
  private:
};

#endif // MYCLASS_H 

ifndef 代表“如果没有定义”。

endif 结束条件。

这可以防止头文件在一个文件中被多次包含。

成员函数

让我们在我们的类中创建一个名为 myPrint() 的示例函数。

MyClass.h

class MyClass
{
  public:
   MyClass();
   void myPrint();
};
MyClass.cpp

#include "MyClass.h"
#include <iostream>
using namespace std;

MyClass::MyClass() {
}

void MyClass::myPrint() {
  cout <<"Hello"<<endl;
}

由于 myPrint() 是一个常规的成员函数,因此有必要在声明和定义中指定它的返回类型。

点操作符

接下来,我们将创建一个 MyClass 类的对象,并使用点(.)运算符调用它的 myPrint() 函数:

#include "MyClass.h"

int main() {
  MyClass obj;
  obj.myPrint();
}

// 输出 "Hello"

指针

我们也可以使用指针来访问对象的成员。

以下指针指向obj对象:

MyClass obj;
MyClass *ptr = &obj;

指针的类型是 MyClass,因为它指向该类型的对象。

箭头成员选择操作符

箭头成员选择操作符( ->)用于通过指针访问对象的成员。

MyClass obj;
MyClass *ptr = &obj;
ptr->myPrint();

使用对象时,使用点成员选择操作符(.)。

使用指向对象的指针时,请使用箭头成员选择操作符( ->)。

常量

常量是一个固定值的表达式。程序运行时不能更改。

使用 const 关键字来定义一个常量变量。

const int x = 42;

所有常量变量在创建时都必须初始化。

常量对象

与内置数据类型一样,我们可以使用 const 关键字使类对象保持不变。

const MyClass obj;

所有 const 变量在创建时都必须被初始化。在类的情况下,这个初始化是通过构造函数完成的。

如果一个类没有使用参数化构造函数进行初始化,则必须提供一个公共的默认构造函数 - 如果没有提供公共的默认构造函数,则会发生编译错误。

一旦 const 类对象已经通过构造函数被初始化,就不能修改对象的成员变量。这包括直接更改公共成员变量和调用成员变量值的成员函数。

当你用 const 来声明一个对象的时候,你不能在对象的生命周期中改变它的数据成员。

只有非 const 对象可以调用非 const 函数。

常量对象不能调用常规函数。因此,对于一个常量的对象来说,你需要一个常量函数。

要将函数指定为 const 成员,const 关键字必须在函数原型之后,在其参数的右括号之外。

对于在类定义之外定义的 const 成员函数,必须在函数原型和定义上使用 const 关键字。

例如:

MyClass.h

class MyClass
{
  public:
    void myPrint() const;
};
MyClass.cpp

#include "MyClass.h"
#include <iostream>
using namespace std;

void MyClass::myPrint() const {
  cout <<"Hello Loen"<<endl;
}

现在 myPrint() 函数是一个常量成员函数。因此,它可以被我们常量对象所调用:

int main() {
  const MyClass obj;
  obj.myPrint();
}
// 输出 "Hello Loen"

尝试从常量对象调用常规函数会导致错误。

另外,当任何 const 成员函数试图改变成员变量或调用 非const 成员函数时,会产生编译器错误。

定义常量对象和函数可确保相应的数据成员不会被意外修改。

成员初始化程序

回想一下,常量是不能改变的变量,所有 const 变量必须在创建时被初始化。

C++ 提供了一个方便的语法来初始化类成员,称为成员初始化列表(也称为构造函数初始值设定)。

成员初始化

例如:

class MyClass {
  public:
   MyClass(int a, int b) {
    regVar = a;
    constVar = b;
   }
  private:
    int regVar;
    const int constVar;
};

这个类有两个成员变量 regVar 和 constVar。有一个构造函数,构造函数有两个参数,用于初始化成员变量。

运行这个代码会返回一个错误,因为它的一个成员变量是一个常量,在声明之后不能赋值。

在像这样的情况下,可以使用成员初始化列表为成员变量赋值。

class MyClass {
 public:
  MyClass(int a, int b)
  : regVar(a), constVar(b)
  {
  }
 private:
  int regVar;
  const int constVar;
};

请注意,在语法中,初始化列表遵循构造函数参数。该列表以冒号(:)开始,然后列出要初始化的每个变量以及该变量的值,并用逗号分隔它们。

使用语法 变量(值) 来分配值。

初始化列表消除了在构造函数体中放置显式赋值的需要。此外,初始化列表不以分号结束。

我们使用单独的头文件和源文件来编写前面的示例。

sum.h

#ifndef SUM_H
#define SUM_H
#include<iostream>
class sum
{
    public:
        sum(int a,int b);

    protected:

    private:
        int reg;
        const int constVar;
};
#endif // SUM_H

sum.cpp

#include "sum.h"
using namespace std;
sum::sum(int a, int b)
: reg(a), constVar(b)
{
    cout << reg << endl;
    cout << constVar << endl;

我们在构造函数中添加了 cout 语句来显示成员变量的值。

下一步是在 main 中创建我们的类的对象,并使用构造函数来赋值。

#include "sum.h"

int main()
{
    sum obj(3,5);
}


/*输出 
3
5
*/

构造函数用于创建对象,通过成员初始化列表将两个参数分配给成员变量。

成员初始化列表可以用于变量,常量。

常量必须使用成员初始化列表进行初始化。

即使在成员不是常量的情况下,使用成员初始值设定语法也是很有意义的。

组合

在现实世界中,复杂的对象通常是使用更小,更简单的对象来组合的。例如,使用金属框架,发动机,轮胎和大量其他部件来组装汽车。

这个过程被称为组合。

在C++中,对象组合涉及使用类作为其他类中的成员变量。这个示例程序演示了组合。它包含 Person 和 Birthday 类,每个 Person 都有一个生日对象作为其成员。

Birthday:

class Birthday {
 public:
  Birthday(int m, int d, int y)
  : month(m), day(d), year(y)
  { 
  }
 private:
   int month;
   int day;
   int year;
};

我们的生日类有三个成员变量。它还有一个使用成员初始化列表来初始化成员的构造函数

为了简单起见,该类在单个文件中被声明。或者,你也可以使用头文件和源文件。
在我们的 Birthday 类中添加一个 printDate() 函数:

class Birthday {
 public:
  Birthday(int m, int d, int y)
  : month(m), day(d), year(y)
  {
  }
  void printDate()
  {
   cout<<month<<"/"<<day
   <<"/"<<year<<endl;
  }
 private:
  int month;
  int day;
  int year;
};

接下来,我们可以创建 Person 类,其中包括 Birthday 类。

#include <string>
#include "Birthday.h"

class Person {
 public:
  Person(string n, Birthday b)
  : name(n),
   bd(b)
  {
  }
 private:
  string name;
  Birthday bd;
};

Person 类有一个 name 和一个 Birthday 成员,并有一个构造函数来初始化它们。

注意: 确保包含相应的头文件。
现在,我们的 Person 类有一个 Birthday 类型的成员:

class Person {
 public:
  Person(string n, Birthday b)
  : name(n),
    bd(b)
  {
  }
 private:
  string name;
  Birthday bd;
};

组合用于共享一个有关系的对象,如 “一个人有一个生日”
在我们的 Person 类中添加一个 printInfo() 函数,打印对象的数据:

class Person {
 public:
  Person(string n, Birthday b)
  : name(n),
  bd(b)
  {
  }
  void printInfo()
  {
   cout << name << endl;
   bd.printDate();
  }
 private:
  string name;
  Birthday bd;
};

请注意,我们可以调用 bd 成员的 printDate() 函数,因为它的类型是 Birthday,它定义了该函数。
现在我们已经定义了 Birthday 和 Person 类,我们可以去 main,创建一个 Birthday 对象,然后把它传递给一个 Person 对象。

int main() {
  Birthday bd(7, 7, 1990);
  Person p("Loen", bd);
  p.printInfo();
}

/*输出
Loen
7/7/1990
*/

我们已经创建了一个日期为 7/7/1990 的生日对象。接下来,我们创建了一个 Person 对象,并将生日对象传递给它的构造函数。

最后,我们使用 Person 对象的 printInfo() 函数打印它的数据。

总的来说,组合可以让每个单独的类相对简单,直接,并专注于执行一项任务。

它还使每个子对象都是独立的,允许重用(我们可以在其他各种类中使用生日类)。

友元函数 friend

通常,类的 private 成员不能从该类以外的地方访问。

但是,声明一个非成员函数作为类的朋友允许它访问类的私有成员。这是通过在类中包含这个外部函数的声明来实现的,并且在关键字 friend 之前。在下面的例子中,someFunc() 不是类的成员函数,它是 MyClass 的一个好友,可以访问它的私有成员。

class MyClass {
 public:
  MyClass() {
   regVar = 0;
  }
 private:
  int regVar;

  friend void someFunc(MyClass &obj);
};

请注意,在将对象传递给函数时,我们需要使用&运算符通过引用来传递它。

函数 someFunc() 被定义为类之外的常规函数​​. 它将 MyClass 类型的对象作为参数,并能够访问该对象的私有数据成员。

class MyClass {
 public:
  MyClass() {
   regVar = 0;
  }
 private:
  int regVar;

 friend void someFunc(MyClass &obj);
};

void someFunc(MyClass &obj) {
  obj.regVar = 42;
  cout << obj.regVar;
}

someFunc() 函数更改对象的私有成员并打印其值。

为了使其成员可访问,类必须在其定义中声明该函数为朋友。

你能把一个函数做为一个类的朋友,除非类“放弃”它对那个函数的友谊。

现在我们可以在 main 中创建一个对象,并调用 someFunc() 函数:

int main() {
  MyClass obj;
  someFunc(obj);
}

//输出 42

someFunc() 有权限修改对象的私有成员并打印它的值。

友元函数的典型用例是在访问两者的私有成员的两个不同类之间进行的操作。

你可以在任何数量的类中声明一个函数的朋友。

与友元函数类似,您可以定义一个朋友类,该类可以访问另一个类的私有成员。

This

C++ 中的每个对象都可以通过称为 this 指针访问自己的地址。

在成员函数内部,这可以用来引用调用对象。

我们来创建一个示例类:

class MyClass {
 public:
  MyClass(int a) : var(a)
  { }
 private:
  int var;
};

友元函数没有这个指针,因为朋友不是一个类的成员。

printInfo() 方法为打印类的成员变量提供了三种选择。

class MyClass {
 public:
  MyClass(int a) : var(a)
  { }
  void printInfo() {
   cout << var<<endl;
   cout << this->var<<endl;
   cout << (*this).var<<endl; 
  }
 private:
  int var;
};

所有三种选择将产生相同的结果。

this 是一个指向对象的指针,所以使用箭头选择操作符来选择成员变量。

为了看到结果,我们可以创建我们的类的一个对象,并调用成员函数。

#include <iostream>
using namespace std;

class MyClass {
 public:
  MyClass(int a) : var(a)
  { }
  void printInfo() {
   cout << var <<endl;
   cout << this->var <<endl;
   cout << (*this).var <<endl; 
  }
 private:
  int var;
};

int main() {
  MyClass obj(45);
  obj.printInfo();
}

/* Outputs
45
45
45
*/

所有这三种访问成员变量的方式都起作用。

请注意,只有成员函数有一个 this 指针。

操作符重载

大多数 C++ 内置操作符都可以重新定义或重载。

因此,操作符也可以与用户定义的类型一起使用(例如,允许您将两个对象添加在一起)。

这个图表显示了可以重载的操作符。

不能重载的运算符包括 :: | .* | . | ?:
让我们声明一个示例类来演示运算符重载:

class MyClass {
 public:
  int var;
  MyClass() {}
  MyClass(int a)
  : var(a) { }
};

我们的类有两个构造函数和一个成员变量。

我们将重载+运算符,以便将我们的类的两个对象添加到一起。

重载操作符是由关键字 operator 定义的函数,后面跟随被定义操作符的符号。

重载操作符类似于其他函数,它具有返回类型和参数列表。

在我们的例子中,我们将重载+运算符。它会返回我们类的一个对象,并把我们类的一个对象作为参数。

class MyClass {
 public:
  int var;
  MyClass() {}
  MyClass(int a)
  : var(a) { }

  MyClass operator+(MyClass &obj) {
  }
};

现在,我们需要定义函数的功能。

我们需要我们的+操作符返回一个新的 MyClass 对象,其成员变量等于两个对象的成员变量之和。

class MyClass {
 public:
  int var;
  MyClass() {}
  MyClass(int a)
  : var(a) { }

  MyClass operator+(MyClass &obj) {
   MyClass res;
   res.var= this->var+obj.var;
   return res; 
  }
};

在这里,我们声明了一个新的 res 对象。然后,我们将当前对象(this)和参数对象(obj)的成员变量的和赋值给 res 对象的 var 成员变量。

这使我们能够在 main 中创建对象,并使用重载的+运算符将它们添加在一起。

int main() {
  MyClass obj1(12), obj2(55);
  MyClass res = obj1+obj2;

  cout << res.var;
}

//输出 67

随着运算符的重载,你可以使用任何自定义逻辑。但是,不可能改变运算符的优先级,分组或操作数的数量。

最后修改:2022 年 12 月 05 日
如果觉得我的文章对你有用,请随意赞赏