今天就是C++与C最大的不同之处了
内容大致包括
- 过程性编程和面对对象编程的区别
- 类的概念
- 如何定义和实现类
- 类的成员函数的定义
- 创建和使用类对象
- 类的构造函数和析构函数
1. 过程性编程和面对对象编程的区别
学过C的同学们都知道,C语言是过程性编程语言,我们设计一个程序的时候首先考虑的是要遵循的步骤。
比如我们要设计统计一个球队的数据,我们首先想到的是我们需要统计的数据有哪些:球员的姓名,号码,命中率等,然后对于这些数据我们给出一些处理方法——函数
那么自然我们会想到用结构体来封装球员的信息,然后定义函数来求平均值或者最大值,再在main函数中给出菜单来实现相应的功能
但是我们从OOP的角度来考虑呢?我们的基本数据元素单元应该是——每一个球员,这个对象中我们不仅要包含基本的数据比如姓名和号码,我们同样可以将一些执行的功能添加,方便调用。
采用OOP方法时,首先从用户的角度考虑对象——描述对象所需的数据以及描述用户与数据交互所需的操作
2. 类的概念
我们需要从复杂的概念中抽象出类,它将数据表示和操纵数据的方法组合成一个整洁的包。
一般来说,类规范由两个部分组成:
类声明:以数据成员的方式描述数据部分,以成员函数(被成为方法)的方式描述公有接口
类方法定义:描述如何实现类成员函数
3. 如何实现和定义类
首先是类的声明,我们用class来定义一个类设计
#include<iostream>
class Dog //这是一个类的声明
{
private://私有
char name[30];
int age;
char color[20];
public://公有
void howl(int time);
void running(int time,char location[]);
void show();
};
以上就是一个简单的类的声明
我们已经介绍了class,这里还有两个东西是新的:public,private
他们描述了对类成员的访问控制。
使用类对象的程序都可以直接访问公有部分,也就是public定义的部分
在上面就是两个函数howl、running、show;
但只能通过公有成员函数(例子中也就是howl、running和show)
或者友元函数(下次博客会提到) 来访问对象的私有成员(private)
也就是说我们如果想要修改private里面各个变量的值,我们必须通过public中的函数来修改
也就是说公有成员函数是程序和对象的私有成员的桥梁,提供了对象和程序之间的接口。
防止程序直接访问数据被称为数据隐藏
ps:private是可以省略的,因为这是类对象的默认访问控制
所以你也可以这样写:
class Dog //这是一个类的声明
{
char name[30];
int age;
char color[20];
public:
void howl(int time);
void running(int time,char location[]);
void show();
};
4. 实现类成员函数
我们需要为那些由类声明中的原型表示的成员函数提供具体代码
这里的函数定义与普通的函数定义类似,有函数体和函数头,也可以有返回类型和参数,但是由于他们是类中的方法所以它们还有以下两个特征:
- 定义成员函数时,必须使用作用域操作符::来标识函数所属的类
- 类方法可以访问类的private内的成员
所以类的第二部分我们可以这样来编写:
void Dog::howl(int time)
{
cout << name <<" has howled for " << time <<" seconds\n";
}
void Dog::running(int time,char location[])
{
cout << name <<" has ran for "<<time<<" seconds in the "<<location;
}
void Dog::show()
{
cout << "please input the dog's name,age and color:\n";
cin >> name;
cin >> age;
cin >> color;
cout << "name : " <<name<<"\n";
cout << "age : "<<age<<"\n";
cout << "color : "<<color << "\n";
}
5. 创建和使用类对象
最后我们加上main函数来实例化这个类
完整代码:
#include <iostream>
using namespace std;
class Dog //这是一个类的声明
{
private://私有
char name[30];
int age;
char color[20];
public://公有
void howl(int time);
void running(int time,char location[]);
void show();
};
//成员函数是可以调用私有成员的
void Dog::howl(int time)
{
cout << name <<" has howled for " << time <<" seconds\n";
}
void Dog::running(int time,char location[])
{
cout << name <<" has ran for "<<time<<" seconds in the "<<location;
}
void Dog::show()
{
cout << "please input the dog's name,age and color:\n";
cin >> name;
cin >> age;
cin >> color;
cout << "name : " <<name<<"\n";
cout << "age : "<<age<<"\n";
cout << "color : "<<color << "\n";
}
int main()
{
Dog dog;//创建一个实例化的类对象
dog.show();//通过.来调用方法
dog.howl(30);
char location[30]="playground";
dog.running(60,location);
return 0;
}
输出结果:
please input the dog's name,age and color:
Peter
2
white
name : Peter
age : 2
color : white
Peter has howled for 30 seconds
Peter has ran for 60 seconds in the playground
这样我们就完成了整个类的构建
6. 类的构建函数和析构函数
我们会发现一个问题——上面的代码中我们初始化私有成员的值时是通过调用函数来实现的,那么我们是否可以像初始化int 型变量一样来初始化类的实例呢?
答案是否定的——聪明的小伙伴们肯定已经知道了原因
上述例子中我们的数据是定义再private中的,数据部分的访问状态是私有的——也就意味着程序是无法访问数据成员的——当然也就无法通过程序进行初始化了
而且上述代码有一个致命的缺陷
如果我们在show()函数调用之前就调用了另外两个方法howl或者running,这时我们的私有成员name、age、color是没有值的
所以我们有没有什么方法能够在创建对象时,自动就对它初始化呢?
没错那就是下面要介绍的构造函数
(在python中我们有方法__init__来初始化)
(C++中我们使用构造函数)
构造函数
专门用来构造新对象,将值赋给他们的数据成员的一类函数
最重要的特征:名称与类名相同!名称与类名相同!名称与类名相同!
所以针对上面给出的代码,我们给出它的一个构造函数
Dog :: Dog(char name[],int age, char color[])//给出构造函数
{
strncpy(m_name, name,30);//这里使用了C++的一个库cstring来复制字符串
m_age = age;
strncpy(m_color,color,20);
}
注意这样一个问题,我们或许习惯用类成员名称作为构造函数的参数,但是如果我们这样做,在复制时就会出现age=age这样的混乱,所以为了避免这样的赋值,我们一般在成员的前面加一个前缀
——这里参考了C++Primer Plus的做法
有了构造函数,我们在声明实例的时候就可以将初始值赋给新对象了
我们一般有两种赋值方法:显式和隐式,在下面的代码中我会给出两种赋值的方法供参考
注意一般我们会给所有类成员做隐式初始化的默认构造函数
也就是说当我们不提供参数的时候,我们一样可以做隐式初始化——这里用到了函数重构的方法
析构函数
用构造函数创建对象后,当对象过期时,程序将自动调用一个特殊的成员函数——析构函数来完成清理工作
既然系统会自动调用,为什么还需要我们来编写析构函数呢?
——如果构造函数使用了new来分配内存,则析构函数应该使用delete来释放这些内存
析构函数的名称也很有趣:在类名前加~
特征:析构函数没有参数!析构函数没有参数!析构函数没有参数!
由于上面的代码中我们没有动态分配空间,所以析构函数不承担任何重要的工作,但是为了显示我们调用了它,我们这样编写析构函数:
Dog :: ~Dog()
{
//这是一个析构函数
cout << "bye! My dear dog, "<<m_name<<"\n";
}
什么时候调用析构函数呢?——这由编译器来决定,通常我们不会显示地调用析构函数,一般在程序结束的时候,系统会自动调用析构函数
7. 完整代码展示
#include <iostream>
#include <stdio.h>
#include <cstring>
using namespace std;
class Dog //这是一个类的声明
{
private://私有
char m_name[30];
int m_age;
char m_color[20];
public://公有
Dog();
Dog(char name[],int age,char color[]);
~Dog();
void howl(int time);
void running(int time,char location[]);
void show();
};
Dog :: Dog()//不提供参数时的默认构造
{
strncpy(m_name, "no_name",30);
m_age=0;
strncpy(m_color,"unknown",20);
}
Dog :: ~Dog()
{
//这是一个析构函数
cout << "bye! My dear dog, "<<m_name<<"\n";
}
Dog :: Dog(char name[],int age, char color[])//提供参数时的构造函数
{
strncpy(m_name, name,30);
m_age = age;
strncpy(m_color,color,20);
}
void Dog::howl(int time)
{
cout << m_name <<" has howled for " << time <<" seconds\n";
}
void Dog::running(int time,char location[])
{
cout << m_name <<" has ran for "<<time<<" seconds in the "<<location<<"\n";
}
void Dog::show()
{
cout << "name : " <<m_name<<"\n";
cout << "age : "<<m_age<<"\n";
cout << "color : "<<m_color << "\n";
}
int main()
{
Dog dog1("Peter",2,"white");//隐式调用构造函数
dog1.show();
dog1.howl(30);
char location[30]="playground";
dog1.running(60,location);
Dog dog2 = Dog();//这里时显式调用 注意默认参数无法进行隐式调用
dog2.show();
return 0;
}
输出结果
name : Peter
age : 2
color : white
Peter has howled for 30 seconds
Peter has ran for 60 seconds in the playground
name : no_name
age : 0
color : unknown
bye! My dear dog, no_name//程序结束是自动调用析构函数
bye! My dear dog, Peter
关于后续的this指针、对象数组、友元函数等内容后续会有更新
点个赞再走呀