对象的使用
源文件和头文件
源文件和头文件
头文件(.h)包含函数声明(原型)和变量声明。
它目前包括一个我们新的 MyClass 类的模板,带有一个默认的构造函数。
MyClass.h
#ifndef MYCLASS_H
#define MYCLASS_H
class MyClass
{
public:
MyClass();
protected:
private:
};
#endif // MYCLASS_H
头文件中的 #ifndef 和 #define 语句稍等解释。
类的实现及其方法在源文件(.cpp)。
目前它只包含一个空的构造函数。
MyClass.cpp
#include "MyClass.h"
MyClass::MyClass()
{
//ctor
}
头文件声明了一个类将做什么,而 cpp 源文件定义了它将如何执行这些功能。
MyClass.h 是头文件。
MyClass.cpp 是源文件。
范围解析运算符
源文件(.cpp)中的双冒号称为作用域解析运算符,用于构造函数定义:
#include "MyClass.h"
MyClass::MyClass()
{
//ctor
}
范围解析运算符用于定义已经声明的特定类的成员函数。 请记住,我们在头文件中定义了构造函数原型。
所以,MyClass::MyClass() 引用 MyClass() 成员函数,或者在这种情况下 MyClass 类的构造函数。
#ifndef & #define
我们为我们的类创建了单独的头文件和源文件,生成的头文件内容如下。
#ifndef MYCLASS_H
#define MYCLASS_H
class MyClass
{
public:
MyClass();
protected:
private:
};
#endif // MYCLASS_H
ifndef 代表“如果没有定义”。
endif 结束条件。
这可以防止头文件在一个文件中被多次包含。
常量
常量是一个固定值的表达式。程序运行时不能更改。
使用 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 成员函数时,会产生编译器错误。
- 定义常量对象和函数可确保相应的数据成员不会被意外修改。
成员初始化
例如:
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;
};
请注意,在语法中,初始化列表遵循构造函数参数。该列表以冒号(:)开始,然后列出要初始化的每个变量以及该变量的值,并用逗号分隔它们。
使用语法 变量(值) 来分配值。
初始化列表消除了在构造函数体中放置显式赋值的需要。此外,初始化列表不以分号结束。
成员初始化程序
我们使用单独的头文件和源文件来编写前面的示例。
MyClass.h
class MyClass {
public:
MyClass(int a, int b);
private:
int regVar;
const int constVar;
};
MyClass.cpp
MyClass::MyClass(int a, int b): regVar(a), constVar(b)
{
cout << regVar << endl;
cout << constVar << endl;
}
我们在构造函数中添加了 cout 语句来显示成员变量的值。
下一步是在 main 中创建我们的类的对象,并使用构造函数来赋值。
#include "MyClass.h"
int main() {
MyClass obj(32, 56);
}
/*输出
32
56
*/
构造函数用于创建对象,通过成员初始化列表将两个参数分配给成员变量。
组合
在现实世界中,复杂的对象通常是使用更小,更简单的对象来组合的。例如,使用金属框架,发动机,轮胎和大量其他部件来组装汽车。
这个过程被称为组合。
在C++中,对象组合涉及使用类作为其他类中的成员变量。这个示例程序演示了组合。它包含 Person 和 Birthday 类,每个 Person 都有一个生日对象作为其成员。
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){}
void printInfo()
{
cout << name << endl;
bd.printDate();
}
private:
string name;
Birthday bd;
};
Person 类有一个 name 和一个 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() 函数打印它的数据。
总的来说,组合可以让每个单独的类相对简单,直接,并专注于执行一项任务。
它还使每个子对象都是独立的,允许重用(我们可以在其他各种类中使用生日类)。
友元函数
通常,类的 private 成员不能从该类以外的地方访问。
但是,声明一个非成员函数作为类的朋友允许它访问类的私有成员。这是通过在类中包含这个外部函数的声明来实现的,并且在关键字 friend 之前。在下面的例子中,someFunc() 不是类的成员函数,它是 MyClass 的一个好友,可以访问它的私有成员。
class MyClass {
public:
MyClass() {
regVar = 0;
}
private:
int regVar;
//注意:这里的someFunc() 不是类的成员函数,它是 MyClass 的一个好友
friend void someFunc(MyClass &obj);
};//从这里开始不属于MyClass类
//这个方法在其他类
void someFunc(MyClass &obj) {
obj.regVar = 42;
cout << obj.regVar;
}
请注意,在将对象传递给函数时,我们需要使用&运算符通过引用来传递它。
现在我们可以在 main 中创建一个对象,并调用 someFunc() 函数:
int main() {
MyClass obj;
someFunc(obj);
}
//输出 42
someFunc() 有权限修改对象的私有成员并打印它的值。
友元函数的典型用例是在访问两者的私有成员的两个不同类之间进行的操作。
你可以在任何数量的类中声明一个函数的朋友。
与友元函数类似,您可以定义一个朋友类,该类可以访问另一个类的私有成员。
现在我们可以在 main 中创建一个对象,并调用 someFunc() 函数:
int main() {
MyClass obj;
someFunc(obj);
}
//输出 42
someFunc() 有权限修改对象的私有成员并打印它的值。
友元函数的典型用例是在访问两者的私有成员的两个不同类之间进行的操作。
你可以在任何数量的类中声明一个函数的朋友。
与友元函数类似,您可以定义一个朋友类,该类可以访问另一个类的私有成员。
This
C++ 中的每个对象都可以通过称为 this 指针访问自己的地址。
在成员函数内部,这可以用来引用调用对象。
我们来创建一个示例类:
class MyClass {
public:
MyClass(int a) : var(a)
{ }
//printInfo() 方法为打印类的成员变量提供了三种选择。
void printInfo() {
cout << var<<endl;
cout << this->var<<endl;
cout << (*this).var<<endl;
}
private:
int var;
};
int main() {
MyClass obj(45);
obj.printInfo();
}
/* 输出结果
45
45
45
*/
友元函数没有这个指针,因为朋友不是一个类的成员。
操作符重载
大多数 C++ 内置操作符都可以重新定义或重载。
因此,操作符也可以与用户定义的类型一起使用(例如,允许您将两个对象添加在一起)。
下面是不可重载的运算符列表:
. :成员访问运算符
.*, ->* :成员指针访问运算符
:: :域运算符
sizeof :长度运算符
?: :条件运算符
# : 预处理符号
我们需要我们的+操作符返回一个新的 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
随着运算符的重载,你可以使用任何自定义逻辑。但是,不可能改变运算符的优先级,分组或操作数的数量。
我们将重载+运算符,以便将我们的类的两个对象添加到一起。
小练习
1.创建一个类(包含.cpp和.h文件)并且有个常量num,它的默认值是10,请对它进行初始化
2.声明个友元函数,对MyClass内的私有成员进行-1并输出
如果你有兴趣的话,欢迎把你的答案在评论区中发表