一、什么是类和对象
简单来说,类是数据和对数据的操作(成员函数/类方法)的组合,而对象是类的实例。
比如人是一个类,小王、小张(具体的某个人)就是这个类的对象。
人的年龄、性别是数据。
对年龄的修改(又长大了一岁age++)就是对数据的操作。
通常,把数据称作类成员,而把函数称作类方法或类成员函数。
二、如何定义一个类
关键字:class
以“人”这一类为例
每个人都有自己的名字,年龄,性别等。
而类中有对这些数据的操作,如设置名字、增加岁数等。
定义如下:
class Person
{
private://这是访问权限,待会会介绍
//数据
string m_name;
int m_age;
string m_gender;
public://访问权限
//对数据的操作
void set_name(string name)
{
m_name=name;
}
void add_name()
{
m_name++;
}
}
可以看到,成员前加了m_,这样有利于区分成员内的变量还是成员外的变量
例如,如果用name作成员变量,则在成员函数set_name(string name)中我们无法知道到底是哪个name。
set_name(string name)
{
name=name;
}
当然,定义一个类时,可以不给出函数的实现,而是后来再给出其实现。此时,要访问类成员函数,则需要作用域解析运算符::,在函数名前加上“类名::”
void Person::set_name(string name)
{
m_name=name;
}
三、类的实例化——对象
如果要创建一个名为“果冻”的人,代码如下
int main()
{
Person Guodong;
}
可以看到,之前已经创建了名为Person的类,如果要实例化,则可以类比 int a;
即“类的名称+实例(对象)的名称”。
四、如何访问类成员以及访问权限
简单来说,就是用成员运算符访问类成员,就像struct一样。
如,要访问成员函数set_name,代码如下:
int main()
{
Person Guodong;
Guodong.set_name(Xuegao);
}
在上述(第二点)例子中,可以看到,相比C语言中的struct,class不仅多了函数,还多了private和public。这就是类的访问权限。
类有三种访问权限:public(公有),private(私有),protected(保护)
public权限,指类内和类外都可以访问到
private和protected权限,指只有类内和类外才能访问到
这里着重介绍前两个。
int main()
{
Person GuoDong;
GuoDong.m_name=Xuegao;//错误!访问权限为private
GuoDong.set_name(Xuegao);//正确!访问权限为public
}
在以上例子中,实例化了一个对象“果冻”,而m_name为私有,setname函数为公有
故在main函数中,不能直接访问m_name,而是要通过set_name函数来访问name。
实际上,将成员属性私有化(private),是C++中很重要的思想。
不仅可以自己控制读写权限,还能检测数据的有效性。
首先看第一个好处——可以自己控制读写权限。
class Person
{
private:
string m_name;//可读可写
int m_age;//可读不可写
string m_gender;//可写不可读
public:
//名字的写
set_name(string name)
{
m_name=name;
}
//名字的读
get_name()
{
return m_name;
}
//年龄的读
get_age()
{
return m_age;
}
//性别的写
set_gender(gender)
{
m_gender=gender;
}
在以上代码中,可以通过set_name和get_name实现对name的读和写,age和gender也是类似。
再来看第二个好处——可以检测数据的有效性。
将成员函数set_age改为如以下函数:
set_age(int age)
{
if(age>200)
{
cout<<"年龄太大,你确定吗?"<<endl;
return;
}
m_age=age;
}
如果输入的年龄超过了200,则不会将数据修改,并且会输出提示消息。这样就检测了数据的有效性。
五、不同对象共用一个类方法
每个对象都有自己的存储空间,用于存储其内部变量和类成员;但同一个类的所有对象共享一组方法(函数)。
例如,声明了Person类的两个对象果冻和雪糕,并分别调用add_name函数
int main()
{
Person Guodong;
Person Xuegao;
Guodong.add_name();
Xuegao.add_name();
}
此时,Guodong.add_name( )和Xuegao.add_name( )执行的是同一块代码,只是将这些代码用于不同的数据(果冻将add_name函数用于自己的m_name,雪糕也是如此)
六、关于类的成员函数:
定义位于类声明中的函数都将自动成为内联函数,因此add_name是一个内联函数。
class Person()
{
private:
int m_age;
public:
void set_name(string name)
{
m_name=name;
}
void add_name()
{
m_age++;
}
}
也可以在类声明之外定义成员函数,并使之成为内联函数。只需使用incline即可,如以下代码:
class Person
{
private:
int m_age;
public:
void set_name(string name)
{
m_name=name;
}
void add_name();
}
incline void Person::add_name()
{
m_age++;
}
七、构造函数和析构函数
1、构造函数:主要是在创建对象时为对象的成员属性附初值,在创建对象时由编译器自动调用。
2、析构函数:主要用于执行一些清理工作,在对象销毁前由编译器自动调用。如构造函数中用了new,则析构函数中使用delete来释放内存。
先来看最普通的构造函数和析构函数
语法:
构造函数:类名(){ ... }
析构函数:~类名(){ ... }
例子:
class Person
{
private:
string m_name;
int m_age;
string m_gender;
Person()
{
cout<<"Person的构造函数的调用"<<endl;
m_age=0;
}
~Person()
{
cout<<"Person的析构函数的调用"<<endl;
}
public:
set_name(string name)
{
m_name=name;
}
}
接下来看看稍微复杂一点的构造函数。
构造函数,按照有无参数,可以分为有参构造函数和无参构造函数,还可以分为普通构造函数和拷贝构造函数。
有参构造函数可以用于类对象的初始化。
class Person
{
//无参构造函数
Person()
{
...
}
//有参构造函数
Person(int a)
{
...
}
//拷贝构造函数
Person(const Person& p)
{
m_age=p.age;
...
}
}
在创建一个对象时,会调用不同的构造函数,并且有多种方法创建一个对象。
1、括号法
int main()
{
Person p1;//调用默认(无参)构造函数
Person p2(10);//调用有残构造函数
Person p3(p2);//调用拷贝构造函数
}
注:为什么调用默认构造函数的时候,不用括号呢?
因为如果有以下代码:
Person p1();
编译器会认为这是一个无参函数,p1为函数名,Person为函数返回类型。
2、显示法
int main()
{
Person p1;//默认构造
Person p2=Person(10);//有参构造
Person p3=Person(p2);//拷贝构造
}
其中,Person(10)为一个匿名对象。
3、隐式转换法
int main()
{
Person p1=10;//有参构造
Person p2=p1;//拷贝构造
}
注:无法使用对象来调用构造函数,因为在构造函数构造出对象之前,对象是不存在的。
八、拷贝构造函数调用时机
1、使用一个已经创建完毕的对象来初始化一个新对象
2、值传递的方式给函数参数传值
3、以值方式返回局部对象
九、构造函数的调用规则
默认情况下,C++编译器至少给一个类添加三个函数:
1、默认构造函数(无参,函数体为空)
2、默认析构函数(无参,函数体为空)
3、默认拷贝构造函数,对属性进行拷贝
规则:
1、若用户定义了有参构造函数,C++不再提供默认构造函数,但是会提供拷贝构造。
2、若用户定义了拷贝构造函数,C++不再提供其他构造函数。
所以若定义了构造函数后,就必须为类提供默认构造函数。且用户定义的默认构造函数通常给所有成员提供隐式初始值。
十、静态成员
1.静态成员变量
特点:(1)所有对象共享一份数据 (2)在编译阶段分配内存 (3)类内声明,类外初始化
class Person
{
//类内声明
public:
static int m_A;
private:
static int m_B;
};
//类外初始化
int Person::m_A=10;
int Person::m_B=10;
//两种访问方式
void test()
{
//1.用对象访问
Person p1;
p1.m_A=100;
//2.用类名访问
cout<<Person::m_A<<endl;
2.静态成员函数
特点:(1)所有对象共享一个函数 (2)静态成员函数只能访问静态成员变量
class Person
{
public:
static int m_A;
int m_B;
static void func()
{
m_A=100;
m_B=100;//错误!m_B不是静态成员变量
}
}
int Person::m_A=100;
//两种访问方式
void test()
{
//1.通过对象访问
Person p1;
P1.func();
//2.通过类名访问
Person::func();
}
十一、深拷贝和浅拷贝
浅拷贝:简单的赋值拷贝操作。
深拷贝:在堆区重新申请空间,进行拷贝操作。
class Person{
public:
Person(){}
Person(int age,int height){
m_age=age;
m_height=new int(height);
}
Person(const Person& p){
m_age=p.m_age;
m_height=new int(*p.m_height);
}
~Person(){
if(m_height!=NULL){
delete m_height;
}
}
public:
int m_age;
int* m_height;
}
参考资料:
1、bilibili 黑马程序员 “黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难”
2、《C++ primer plus 第6版》