导语
C++是由C发展而来的一种语言,可以看作是对C的一种扩展。最初这种语言被称作“带类的C(C with Classes)”,在上个世纪八十年代更名为C++。相较于C,C++主要提供了面向对象编程的能力。本文旨在帮助对C/C++有粗浅了解的初学者快速学习C++的类的基础知识,希望读者在阅读之前能够掌握C/C++有关函数、控制语句、运算符的相关知识。
结构体与类具有相似等价性,了解结构体的可以类比学习。两者在初学的层面几乎没有差别,但在后面的章节了解到类的多态等性质两者才真正有区分。
1. 面向对象
为了高效便捷地构建一个软件,C++为我们提供了一个强大的功能,即面向对象编程。在此不过多赘述其定义,我们用一个例子来解释。
假设你现在在制作一款赛车小游戏,需要生成一些随机赛车,同时玩家也能定制自己的赛车。此时很自然地就会想到,我们不可能穷尽所有的可能将所有赛车做出然后需要时调用,耗时耗力,复用性低。所以我们通常尽可能地提取赛车的各种属性以及行为。例如,属性:款式、颜色、最大时速······行为:加速、左转、右转、刹车······然后当我们需要时,将这个抽象的赛车类实例化出一个特定的赛车。这极大增强了代码的复用性和可拓展性——我们不需要对各个对象都设计相关的行为函数,它们共享我们已设计的函数。
以上只是一个简单的例子,并不能展现出面向对象编程的强大之处,我们将在后续的学习中了解掌握。
2.类的定义与使用
2.1. 基本定义
首先我们来定义一个简单的类。下面以“雇员类”为例子。
#include <iostream>
#include <string>
class Employee
{
public: //公有
void print()
{
std::cout << "This employee "<< name <<" gets "<< wage
<< " every month." <<std::endl;
}
void setWage(int Wage)//设置工资
{
wage = Wage;
}
void setName(std::string Name)//设置名字
{
name = Name;
}
int getWage() const
{
return wage;
}
std::string getName() const
{
return name;
}
private://私有
int wage;
std::string name;
};// 注意不要漏“;”
int main()
{
std::cout << "第一个对象并设置:" <<std::endl;
Employee object1;
object1.setWage(1000);
object1.setName("Molly Jones");
object1.print();
std::cout << "第二个对象并设置:" <<std::endl;
Employee object2;
object2.setWage(1500);
object2.setName("Desmond");
object2.print();
std::cout << "第二个对象设置后的第一个对象:" <<std::endl;
object1.print();
return 0;
}
上述代码为我们创建了一个Employee类,并用一个main函数进行测试。结果如下:
代码的第4行到第35行我们定义了一个类。class关键字后跟着类名,若舍弃大括号中包含的内容,直接用一个分号结束,我们称之为类声明。
class Employee;
而大括号包含着的总称类定义,它包含了类的各个成员。类可以拥有两类成员:数据成员和成员函数。在雇员类中,数据成员指的是第33和第34行的wage和name,或者我们称其为“属性”;而雇员类中的成员函数从第7行开始到第31行结束,我们也可以称其为“行为”。它们的特点是,都要在类内且不再任何函数内被声明,否则函数称为全局函数,数据称为全局变量或者局部变量(取决于其是否在函数中被声明,且即使在成员函数中声明的变量仍然不是数据成员)。
以下可能需要作用域的相关知识:
“public:"和"private:"是访问权限指示符或成员访问说明符,缺省状态下为"private"。很显然,public下的是公有成员,即在类外也可以被访问;private下的是私有成员即只有在类内可以被直接访问。
类可以被看成一种程序员使用各种数据类型组合而成的一个新的数据类型,被称作抽象数据类型。因此,它可以被仿照我们声明各种基本数据一样,实例化(抽象数据类型我们称之为实例化)出各种对象(我们称其为对象)。
通常来说,对于个各种数据成员我们要保持其私有的性质,使其安全地被封装在类内,不能轻易地在类外被破坏。如果要修改数据成员,必须如上写出get和set函数。以上出自软件工程的目的,所以还要根据实际情况考虑。例如不需要修改的一些数据成员不需要提供set函数。命名也不能固化,例如存钱不能命名为set函数。
在main函数中,我们看到实例化了一个变量名为"object1"的对象,并通过点操作符(.)通过对象调用成员函数。成员函数在类外能被调用,首先需要是公有的成员,其次要有一个句柄,这个句柄就是对象加上点操作符。后面我们会学到其他的句柄。
根据结果,我们发现不同对象虽然他们数据成员名相同,但是互不干扰,而且还可以共享这些函数。实际上,对象在内存空间中所占据的大小仅取决于这些数据成员,这与结构体也是如出一辙的。
2.1. 构造函数
上面的例子向我们展示了类的基本定义,这不禁让我们思考:假如我们没有通过set函数设置数据成员的值会发生什么呢?
#include <iostream>
#include <string>
class Employee
{
public:
void print()
{
std::cout << "This employee " << name << " gets " << wage
<< " every month." << std::endl;
}
void setWage(int Wage)
{
wage = Wage;
}
void setName(std::string Name)
{
name = Name;
}
int getWage() const
{
return wage;
}
std::string getName() const
{
return name;
}
private:
int wage;
std::string name;
};
int main()
{
Employee object1;
object1.print();
return 0;
}
结果如下:
This employee gets 333918856 every month.
name为空,wage为一个毫无规律的数字。
对于数据成员,若未初始化也未赋值,情况视类型而定。
对于基本数据类型,值是一个垃圾数据;对于抽象数据类型的值一般是空,如string,但取决于string库中的定义。
那么是怎么定义的呢?实际上string也是一个类,了解类的初始化方法就可以知道为什么抽象数据类型string的初始化是空字符串了。
这边就引入了新的一类函数:构造函数。
构造函数有以下特点:
1.与类同名
2.不能返回任何值
3.通常声明为public,若为private,一般会导致无法实例化对象,编译器报错
4.在实例化一个对象时调用
构造函数有缺省构造函数和非缺省的构造函数。缺省指的是没有传入任何参数,或者可以不传入任何参数(知识点:函数默认的参数)。缺省构造函数又称默认构造函数,来源分为两类,系统提供的和程序员定义的。当程序员定义了任何形式的构造函数,无论是否缺省,编译器都不会再提供缺省的构造函数了,若此时程序员提供的构造函数为private,编译器无法调用程序员定义的构造函数,也无法利用编译器自身地构造函数(未提供),就无法创建对象,除非通过显式地声明,这个后面再提及。
构造函数可以多个。以下是一个例子。
#include <iostream>
#include <string>
class Employee
{
public:
Employee() //the Constructor 用户提供的缺省构造函数
:wage(500), name("Initial")//初始化列表(初始化与赋值不同)
{
//你也可以
/*
setWage(500);
setName("Jane");
但这不叫初始化
*/
std::cout << "缺省构造函数被调用" << std::endl;
}
Employee(int w, std::string n)
:wage(w), name(n)
{
/*
setWage(w);
setName(n);
但这不叫初始化
*/
std::cout << "非缺省构造函数被调用" << std::endl;
}
void print()
{
std::cout << "This employee " << name << " gets " << wage
<< " every month." << std::endl;
}
void setWage(int Wage)
{
wage = Wage;
}
void setName(std::string Name)
{
name = Name;
}
int getWage() const
{
return wage;
}
std::string getName() const
{
return name;
}
private:
int wage;
std::string name;
};
int main()
{
Employee object1;
object1.print();
Employee object2(1000, "Molly Jones");
object2.print();
return 0;
}
我们为上面的类提供了一个缺省的构造函数,利用初始化列表将wage初始化为500,名字为Initial。