在python中,我们提到过类这个概念。所谓类,就是一个包含着元素和函数的数据类型,在C++中,系统数据类型(如int、char等)都是由类进行定义的,我们当然也可以定义自己需要的数据类型。类是面对对象编程的重要概念,我们可以把它理解成一系列特征的集合。对象则是类的具象化,他可以拥有类中包含的多个特征,而具体要包含哪些特征则需要根据实际需求确定。类中存储的数据叫做类的属性,包含的函数叫做类的方法。
公有和私有的概念用于规定类定义的属性与方法在哪里可以被使用。通常,公有的内容可以在类内进行使用,也可以在类外进行使用,而私有的内容则只能在类内直接使用。在python中,类里创建的成员默认都是公有的,然而C++却必须要我们声明哪些内容是私有,而哪些内容是公有的。如果我们不加以声明,计算机就会报错。下面我们举一个简单的例子,初步了解类的定义以及公有和私有的概念:
class Student // 定义一个Student类
{
public: // 定义共有内容,以定义name属性为例
std::string name;
private: // 定义私有内容,以定义age属性为例
int age;
};
这样定义的Student类型,就有了两个属性,其中name为公有属性。这样的类与C语言中结构体有些相似,但类除了可以集成变量,还能够集成函数。
类中不光有数据,还可以有函数。类中包含的函数叫做类的成员函数,也叫做方法。类方法的声明和定义与面对过程编程中函数的声明和定义很像,我们可以在类中先声明方法,然后再类外进行定义,也可以把声明和定义放在一起。当然,在声明方法之前,我们也需要告诉计算机该方法是私有还是公有:
class Student
{
public:
std::string name;
int grade(int Chinese,int English,int math);
private:
int age;
};
int Student::grade(int Chinese,int English,int math)
{
return Chinese+English+math;
}
以上是在类外定义方法的情况,后面的例子大多是在类内定义方法,这里不做赘述。需要注意的是,在类内定义的方法都是内联函数,而类外定义的方法需要有inline修饰才是内联函数。设置成私有属性时声明和定义的方法与公有一样。
类有了定义,我们就需要了解一下如何使用类。通常,使用类需要对类进行实例化,所谓实例化,就是将抽象的类具象化为一个实例:
// 这里使用的Student类为第三节中定义的类
int main()
{
Student ZhangSan; // ZhangSan为类的一个实例
ZhangSan.name="ZhangSan"; // 给实例ZhangSan的name赋值
// ZhangSan.age=20; // 报错,无法赋值
cout<<ZhangSan.name<<" "<<ZhangSan.grade(100,99,98)<<endl;
}
// 输出为:ZhangSan 297
从这个例子中可以看出,我们实例化一个Student类型的变量ZhangSan之后,可以用点号运算符调用ZhangSan所在类的公共属性与方法,但是私有属性是不可以被调用的。这与python的类很像。
构造参数在python中我们也学习过,它是在该类被实例化后就会立刻自动执行的方法。比如我们使用 int a 声明一个参数a,那么a会被自动赋予初值,这个过程就是依赖构造函数实现的。在C++中,构造函数是与类同名的方法,如果需要使用构造函数,首先需要定义一个没有参数的构造函数,如:
class Student
{
public:
std::string name;
int age;
Student() // 构造函数没有返回值,也可以在函数外面定义
// 这里实现给两个方法赋值的功能
{
age=0;
name="ZhangSan";
}
};
我们再来实例化一个ZhangSan,然后看看他的属性是什么:
int main()
{
Student ZhangSan;
cout<<ZhangSan.name<<" "<<ZhangSan.age<<endl;
}
// 输出为:ZhangSan 0
这就很像实例化一个int类的a,a会自动获得初值。但是,我们在对int类进行实例化时,也可以给这个实例一个值,如int a=10。C++若想实现这个功能,就需要对构造函数进行重载。下面这个例子我会同时演示重载构造函数以及在类外定义构造函数:
class Student
{
public:
string name;
int age;
Student(); // 想定义有参数构造函数时,无参数构造函数也不可以省略
Student(int a,string b);
};
Student::Student()
{
age=0;
name="ZhangSan";
}
Student::Student(int a,string b)
{
age=a;
name=b;
}
但是我们要注意,在实例化Student类后,可不能用等于号为实例赋age和name。调用有参数的构造函数只能这样写:
int main()
{
Student LiSi(20,"LiSi");
cout<<LiSi.name<<" "<<LiSi.age<<endl;
}
当然,我们还可以用初始化列表为属性赋值,这个我们放在下节介绍。
python的构造函数就是__init__(),由于可变参数、默认参数、self参数的存在,很多C++中需要函数重载才能实现的功能在python中可以非常容易的实现。
在这里我借题发挥,给大家简单介绍一下动态空间开辟。在python中,如果我们想给如列表这样的类型增加或删除元素,可以直接调用append、pop之类的方法,这是因为列表类是允许改变其实例所占空间大小的。然而,在C++中,一个类型的大小是在声明时就已经决定的。如果想要改变其大小,就需要进行动态空间开辟。动态空间开辟一旦开始,就必须在适当的位置停止,否则会留下隐患。
析构函数的定义方法与构造函数很像,只是它的函数名是“~类名”。析构函数的主要是在合适的时机(如对象超出其作用域或者程序结束时)自动运行,销毁需要销毁的内容(如类内动态开辟的空间)。举一个简单的例子,看不懂的小伙伴删除标记位置的代码再看即可,析构函数不做重点介绍,大家看一下他是在什么时候执行即可:
class Student
{
public:
int age;
// 动态空间开辟:
/*不懂可删*/int* ptr1=new int;
/*不懂可删*/int* ptr2=new int[10];
string name;
Student()
{
age=0;
name="ZhangSan";
}
~Student() // 析构函数没有参数,需要写清理代码。写其他内容也不影响执行
// 当类中不存在需要手动清理的内容,就不需要自定义析构函数
{
/*不懂可删*/delete ptr1; // 清除动态开辟的单个类型
/*不懂可删*/delete [] ptr2; // 清除动态开辟的数组类型
cout<<"析构函数已执行!"<<endl;
}
int grade(int Chinese,int English)
{
return Chinese+English;
}
};
int main()
{
Student ZhangSan;
cout<<ZhangSan.age<<ZhangSan.name<<endl;
cout<<ZhangSan.grade(98,100)<<endl;
cout<<"我们不再需要张三了"<<endl;
}
// 输出为:0ZhangSan
// 198
// 我们不再需要张三了
// 析构函数已执行!
有兴趣的效果版可以debug一下,在这个例子中析构函数是在主函数内容都已经运行完成后才返回到实例化Student类的那一步执行的。
常成员函数也是类方法的一种,只需在声明和定义函数后再加一个const标识即可。常成员函数可以读取类属性,但是不能对其进行修改;常成员函数能且仅能调用常成员函数,这是为了防止常成员函数调用普通成员函数间接修改类内成员。举例如下:
class Student
{
public:
int age;
string name;
Student()
{
age=0;
name="ZhangSan";
}
void amand(int a) //
{age=a;}
bool read () const // 定义常成员函数,代表该函数为常成员函数,只读不写
{
// amand(10); // 由于amand函数非常成员函数,因此不能被调用
cout<<name;
cout<< age;
return true;
}
};
int main()
{
Student ZhangSan;
ZhangSan.read();
}
静态函数和静态数据用于描述全局,作用域与主函数相同,静态内容与该类有关,但又是独立于具体实例的存在。静态属性可以在任何位置被调用和修改,但是一旦被修改,所有的实例中调用的静态属性值都会产生变化。比如我们需要统计一共实例化了多少个Student类,就可以借助静态成员函数实现:
class Student
{
public:
int age;
string name;
static int number; // 申请number参数记录实例化的学生总数
Student()
{
age=20;
name="ZhangSan";
number++;
}
Student(int a,string b)
{
age=a;
name=b;
number++;
}
};
int Student::number=0; // 为静态属性赋初值,不符初值无法运行
测试一下:
int main()
{
Student ZhangSan;
cout<<ZhangSan.age<<" "<<ZhangSan.name<<" "<<ZhangSan.number<<endl;
Student LiSi(25,"LiSi"); // 调用第二个构造函数(重载的构造函数)
cout<<LiSi.age<<" "<<LiSi.name<<endl;
cout<<LiSi.number<<" "<<ZhangSan.number<<" "<<Student::number<<endl; // 三种查看number的方法
// 前两种查看方法是为了展示共享数据
LiSi.number++; // 通过类的实例也可以修改静态属性或调用静态方法,但这并不符合静态属性的设计初衷,
// 因此使用静态内容时最好用类进行引用,如下所示
cout<<Student::number<<endl;
}
// 运行结果:20 ZhangSan 1
// 25 LiSi
// 2 2 2
// 3
除了静态属性,C++还有静态方法。静态方法只能访问静态属性和调用静态方法,但是非静态方法可以调用静态方法。依旧是一个简单的例子:
class Student
{
public:
int age;
string name;
Student()
{
age=20;
name="ZhangSan";
}
Student(int a,string b)
{
age=a;
name=b;
}
static void identity() // 用于展示这些学生的母校
{
cout<<"S University Student"<<endl;
}
};
我们可以在主函数中调用静态方法:
int main()
{
Student ZhangSan;
ZhangSan.identity(); // 可以从实例中调用静态属性
Student::identity(); // 也可以从类中直接调用
}
// 输出为:S University Student
// S University Student
这节我带大家入门了C++的面对对象编程,希望大家重点理解类和对象,掌握类的属性和方法的定义与调用,以及如何实例化,需要重点理解构造函数。析构函数、常成员函数和静态属性静态方法我们了解一下就好,至于公有和私有的概念,下一节还会重点讲解。