目录
将事物的属性和行为封装到一个类中,属性和行为作为一个整体,称为封装。
类中的属性称为成员属性或数据成员;
类中的行为称为成员函数或成员方法;
类中的属性和行为有三种访问权限:public、protected、private,默认的访问权限为private。
public权限成员类内可以访问,类外也可以访问;
protected权限成员类内可以访问,类外不可以访问;
private权限成员类内可以访问,类外不可以访问;
protected权限成员和private权限成员区别是,子类可以访问父类protected权限成员,而不可以访问父类private权限成员;
此处的类内指类定义的内部,类外指类定义的对象;
class和struct的区别:class的默认权限为private,struct默认权限为public;
成员属性私有化:即将类中的属性权限设置为private,通过public权限的成员函数控制成员属性的读或写,有两个优点:可以控制成员属性的读写权限;检测数据的有效性;
(1),基本概念
构造函数主要用于创建对象时对对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用;
析构函数主要用于在对象销毁前系统自动调用,执行一些清理工作;
构造函数语法:类名(){}
构造函数没有返回值,也不写void;
函数名与类名相同;
构造函数可以有参数,可以发生重载;
程序在调用对象时候会自动调用构造函数,无需手动调用,而且只会调用一次;
析构函数语法:~类名(){}
析构函数没有返回值也不写void;
函数名称与类名相同,在名称前加上~;
析构函数不可以有参数,因此不可以重载;
程序在对象销毁前自动调用析构函数,无需手动调用,而且只会调用一次;
注意:构造函数和析构函数都是在public权限下。
#include <iostream>
using namespace std;
#include <string>
class person
{
public:
person()
{
cout << "person的构造函数调用" << endl;
}
~person()
{
cout << "person的析构函数调用" << endl;
}
};
void test()
{
person p;
}
int main()
{
test();
system("pause");
return 0;
}
运行结果:
person的构造函数调用
person的析构函数调用
请按任意键继续. . .
(2),构造函数的分类及调用
按是否有参数分为:无参构造函数和有参构造函数;
按类型可分为:普通构造函数和拷贝构造函数;
构造函数调用方式:括号法、显示法、隐式转换法;
括号法语法:
#include <iostream>
using namespace std;
#include <string>
class person
{
public:
person()
{
cout << "person的无参构造函数调用" << endl;
}
person(int a)
{
cout << "person的有参构造函数调用" << endl;
}
person(const person& a)
{
cout << "person的拷贝构造函数调用" << endl;
}
~person()
{
cout << "person的析构函数调用" << endl;
}
};
void test()
{
//括号法
person p1; //无参构造函数
person p2(10);//有参构造函数
person p3(p2);//拷贝构造函数
}
int main()
{
test();
system("pause");
return 0;
}
运行结果:
person的无参构造函数调用
person的有参构造函数调用
person的拷贝构造函数调用
person的析构函数调用
person的析构函数调用
person的析构函数调用
请按任意键继续. .
注意:使用括号法时,无参构造函数的调用不能加括号,如person p();此种语法编译器会认为是函数的声明,函数返回类型是person,函数名是p。
显示法:
#include <iostream>
using namespace std;
#include <string>
class person
{
public:
person()
{
cout << "person的无参构造函数调用" << endl;
}
person(int a)
{
cout << "person的有参构造函数调用" << endl;
}
person(const person& a)
{
cout << "person的拷贝构造函数调用" << endl;
}
~person()
{
cout << "person的析构函数调用" << endl;
}
};
void test()
{
//显示法
person p1;
person p2 = person(10); //有参构造函数定义
person p3 = person(p2); //拷贝构造函数调用
}
int main()
{
test();
system("pause");
return 0;
}
运行结果:
person的无参构造函数调用
person的有参构造函数调用
person的拷贝构造函数调用
person的析构函数调用
person的析构函数调用
person的析构函数调用
请按任意键继续. . .
注意:
匿名对象:person(10),匿名对象的特点,当前行运行后,系统会立即回收匿名对象。
#include <iostream>
using namespace std;
#include <string>
class person
{
public:
person()
{
cout << "person的无参构造函数调用" << endl;
}
person(int a)
{
cout << "person的有参构造函数调用" << endl;
}
person(const person& a)
{
cout << "person的拷贝构造函数调用" << endl;
}
~person()
{
cout << "person的析构函数调用" << endl;
}
};
void test()
{
//显示法
person(10); //匿名对象
cout << "aaaaa" << endl;
}
int main()
{
test();
system("pause");
return 0;
}
匿名对象的运行:
person的有参构造函数调用
person的析构函数调用
aaaaa
请按任意键继续. . .
注意:不要使用显示法调用拷贝构造函数初始化匿名对象 ,因为person(p2)编译器会识别为peson p2,如果p2已存在会出现二义性。
隐式转换法:
#include <iostream>
using namespace std;
#include <string>
class person
{
public:
person()
{
cout << "person的无参构造函数调用" << endl;
}
person(int a)
{
cout << "person的有参构造函数调用" << endl;
}
person(const person& a)
{
cout << "person的拷贝构造函数调用" << endl;
}
~person()
{
cout << "person的析构函数调用" << endl;
}
};
void test()
{
//隐式转换法
person p1 = 10;//有参构造函数
person p2 = p1;//拷贝构造函数
}
int main()
{
test();
system("pause");
return 0;
}
运行结果:
person的有参构造函数调用
person的拷贝构造函数调用
person的析构函数调用
person的析构函数调用
请按任意键继续. . .
(3),拷贝构造函数调用时机
拷贝构造函数的调用时机分为三类:
第一类:使用一个已经创建的对象初始化一个新对象:
#include <iostream>
using namespace std;
#include <string>
class person
{
public:
person()
{
cout << "person的无参构造函数调用" << endl;
}
person(int a)
{
cout << "person的有参构造函数调用" << endl;
}
person(const person& a)
{
cout << "person的拷贝构造函数调用" << endl;
}
~person()
{
cout << "person的析构函数调用" << endl;
}
};
void test()
{
person p1(10);//有参构造函数
person p2(p1);//拷贝构造函数
}
int main()
{
test();
system("pause");
return 0;
}
第二类:值传递的方式给函数传值
#include <iostream>
using namespace std;
#include <string>
class person
{
public:
person()
{
cout << "person的无参构造函数调用" << endl;
}
person(int a)
{
cout << "person的有参构造函数调用" << endl;
}
person(const person& a)
{
cout << "person的拷贝构造函数调用" << endl;
}
~person()
{
cout << "person的析构函数调用" << endl;
}
};
void dowork(person p)
{
}
void test()
{
person p(10);
dowork(p);
}
int main()
{
test();
system("pause");
return 0;
}
运行结果:
person的有参构造函数调用
person的拷贝构造函数调用
person的析构函数调用
person的析构函数调用
请按任意键继续. . .
需要注意的是,当函数的形参是引用类型时,不会调用到拷贝构造函数,下边代码中将函数dowork的形参类型修改为person& p
#include <iostream>
using namespace std;
#include <string>
class person
{
public:
person()
{
cout << "person的无参构造函数调用" << endl;
}
person(int a)
{
cout << "person的有参构造函数调用" << endl;
}
person(const person& a)
{
cout << "person的拷贝构造函数调用" << endl;
}
~person()
{
cout << "person的析构函数调用" << endl;
}
};
void dowork(person& p)
{
}
void test()
{
person p(10);
dowork(p);
}
int main()
{
test();
system("pause");
return 0;
}
运行结果:
person的有参构造函数调用
person的析构函数调用
请按任意键继续. . .
第三类:值方式返回局部对象
#include <iostream>
using namespace std;
#include <string>
class person
{
public:
person()
{
cout << "person的无参构造函数调用" << endl;
}
person(int a)
{
cout << "person的有参构造函数调用" << endl;
}
person(const person& a)
{
cout << "person的拷贝构造函数调用" << endl;
}
~person()
{
cout << "person的析构函数调用" << endl;
}
};
person dowork()
{
person p;
return p;
}
void test()
{
dowork();
}
int main()
{
test();
system("pause");
return 0;
}
运行结果:
person的无参构造函数调用
person的拷贝构造函数调用
person的析构函数调用
person的析构函数调用
请按任意键继续. . .
同样的,如果函数的返回值类型是引用,也不会调用到拷贝构造函数,下边代码中将函数dowrok的返回值类型设置为person&
#include <iostream>
using namespace std;
#include <string>
class person
{
public:
person()
{
cout << "person的无参构造函数调用" << endl;
}
person(int a)
{
cout << "person的有参构造函数调用" << endl;
}
person(const person& a)
{
cout << "person的拷贝构造函数调用" << endl;
}
~person()
{
cout << "person的析构函数调用" << endl;
}
};
person& dowork()
{
person p;
return p;
}
void test()
{
dowork();
}
int main()
{
test();
system("pause");
return 0;
}
运行结果:
person的无参构造函数调用
person的析构函数调用
请按任意键继续. . .
只在创建类对象p时调用了无参构造函数。
(4),构造函数调用规则
默认情况下,C++编译器给一个类至少提供三个函数:
1,默认构造函数(无参,函数体为空);
2,默认析构函数(无参,函数体为空);
3,拷贝构造函数,对属性进行值拷贝;
如果用户定义了有参构造函数,C++编译器就不提供默认构造函数,但是会提供拷贝构造函数;
如果用户定义了拷贝构造函数,C++编译器就不提供有参构造函数和默认构造函数(无参构造函数);
注意:编译器本身不会提供有参构造函数。
(5),深拷贝和浅拷贝
浅拷贝:编译器提供的拷贝构造函数进行的对象属性拷贝操作;
浅拷贝带来的问题:会出现堆区内存的重复释放。因此需要使用深拷贝进行属性的拷贝操作,即自定义拷贝构造函数在堆区创建内存。
深拷贝:即在拷贝构造函数调用时,重新在堆区申请内存,将被拷贝对象存放在堆区的数据拷贝到重新申请的堆区内存中。
#include <iostream>
using namespace std;
#include <string>
class person
{
public:
person()
{
cout << "person的无参构造函数调用" << endl;
}
person(int age,int height)
{
m_Age = age;
m_Height = new int(height);
cout << "person的有参构造函数调用"<< endl;
}
person(const person& a)
{
m_Age = a.m_Age;
m_Height = new int(*a.m_Height);
cout << "person的拷贝构造函数调用" << endl;
}
~person()
{
cout << "person的析构函数调用" << endl;
if (m_Height != NULL)
{
delete m_Height;
m_Height = NULL;
}
}
int m_Age;
int* m_Height;
};
void test()
{
person p1(18, 160);
cout << "p1的年龄为:" << p1.m_Age << "身高为:" << *p1.m_Height << endl;
person p2(p1);
cout << "p2的年龄为:" << p2.m_Age << "身高为:" << *p2.m_Height << endl;
}
int main()
{
test();
system("pause");
return 0;
}
下图中为浅拷贝存在问题:
通过深拷贝解决问题释义图:
(6),初始化列表
C++提供初始化列表语法,用来对属性初始化;
语法:构造函数():属性1(值1),属性2(值2),......{}
#include <iostream>
using namespace std;
#include <string>
class person
{
public:
person()
{
cout << "person的无参构造函数调用" << endl;
}
person(int a ,int b, int c):m_A(a),m_B(b),m_C(c)
{
cout << "person的有参构造函数调用"<< endl;
}
person(const person& a)
{
cout << "person的拷贝构造函数调用" << endl;
}
~person()
{
cout << "person的析构函数调用" << endl;
}
int m_A;
int m_B;
int m_C;
};
void test()
{
person p(10, 20, 30);
cout << "m_A = " << p.m_A << endl;
cout << "m_B = " << p.m_B << endl;
cout << "m_C = " << p.m_C << endl;
}
int main()
{
test();
system("pause");
return 0;
}
运行结果:
person的有参构造函数调用
m_A = 10
m_B = 20
m_C = 30
person的析构函数调用
请按任意键继续. . .
(7),类对象做类成员-对象成员
一个类的对象作为另一个类的成员,如下边语法中A类的对象a作为B类的成员。
语法:
class A{};
class B
{
A a;
}
类对象做类成员的应用场景:
#include <iostream>
using namespace std;
#include <string>
class phone
{
public:
phone(string brand)
{
m_Brand = brand;
cout << "phone构造函数执行" << endl;
}
~phone()
{
cout << "phone析构函数执行" << endl;
}
string m_Brand;
};
class person
{
public:
person(string Name, string Phone) :m_Name(Name), m_Phone(Phone)
{
cout << "person构造函数执行" << endl;
}
~person()
{
cout << "person析构函数执行" << endl;
}
string m_Name;
phone m_Phone;
};
void test()
{
person p("张三", "huawei");
}
int main()
{
test();
system("pause");
return 0;
}
运行结果:
phone构造函数执行
person构造函数执行
person析构函数执行
phone析构函数执行
请按任意键继续. . .
可以看到,phone类对象做类person成员时, 先创建phone类对象再创建person类对象,析构时先析构person类对象再析构phone类对象。
(8),静态成员
在成员变量和成员函数之前加上static关键字,称为静态成员;
静态成员变量:
所有对象共享同一份数据,因此一个对象赋值后,其他对象的这个变量也为同样的值;
在编译阶段分配内存,在全局区分配内存;
类内声明,类外初始化;
静态成员函数:
所有对象共享同一个函数;
静态成员函数只能访问静态成员变量;
#include <iostream>
using namespace std;
#include <string>
//静态成员
//静态属性-对象共享同一份数据
class Student
{
public:
static string Sex;
int Age;
//静态函数,只能访问静态属性,不能访问非静态属性
static void function()
{
cout << "static函数运行" << endl;
Sex = "Male";
}
};
string Student::Sex = "Female"; //静态属性,类内声明,类外要初始化
void test01()
{
Student stu1;
stu1.Age = 10;
//cout << "stu1的年龄:" <<stu1.Age<< endl;
//cout<< "stu1的性别:" << stu1.Sex<<endl;
Student stu2;
stu2.Age = 11;
stu2.Sex = "Male";
//cout << "stu2的年龄:" << stu2.Age << endl;
//cout << "stu2的性别:" << stu2.Sex << endl;
//cout << "stu1的年龄:" << stu1.Age << endl;
//cout << "stu1的性别:" << stu1.Sex << endl;
//静态属性访问方式,直接通过类名+::+属性名进行访问
cout << "stu1的性别:" << Student::Sex << endl;
cout << "stu2的性别:" << Student::Sex << endl;
}
void main()
{
test01();
system("pause");
return;
}
运行结果:
stu1的性别:Male
stu2的性别:Male
请按任意键继续. . .
注意:静态成员变量需要在类内声明,类外初始化。
从所占内存角度分析静态成员:
#include <iostream>
using namespace std;
#include <string>
class A{}; //不包括任何成员的类,所占内存空间为1
class B //只包含一个静态成员变量的类
{
static int a;
};
int B::a = 0;
class C //包含一个非静态成员变量的类
{
int b;
};
class D //包含一个静态成员函数的类
{
static void func()
{
}
};
class E //包含一个非静态成员函数的类
{
void func()
{
}
};
void test()
{
A a;
cout << "不包括任何成员的类对象所占空间:" << sizeof(a) << endl;
B b;
cout << "只包含一个静态成员变量的类对象所占空间:" << sizeof(b) << endl;
C c;
cout << "只含一个非静态成员变量的类对象所占空间:" << sizeof(c) << endl;
D d;
cout << "只含一个静态成员函数的类对象所占空间:" << sizeof(d) << endl;
E e;
cout << "只含一个非静态成员函数的类对象所占空间:" << sizeof(e) << endl;
}
int main()
{
test();
system("pause");
return 0;
}
运行结果:
不包括任何成员的类对象所占空间:1
只包含一个静态成员变量的类对象所占空间:1
只含一个非静态成员变量的类对象所占空间:4
只含一个静态成员函数的类对象所占空间:1
只含一个非静态成员函数的类对象所占空间:1
请按任意键继续. . .
因为所有对象共享同一个静态成员变量和静态成员函数,因此可以通过以下格式访问
类名:: 静态成员名;
注意:
静态成员也有权限,类外访问不到私有的静态成员;
静态成员函数只能访问静态成员变量,不能访问非静态成员变量;
(9),拷贝构造函数形参类型
下边代码中的拷贝构造函数的形参类型为当前类的应用,并且是const引用。
为什么拷贝构造函数的形参类型是当前类的const引用呢?
#include <iostream>
using namespace std;
#include <string>
class person
{
public:
person()
{
cout << "person的无参构造函数调用" << endl;
}
person(int a)
{
cout << "person的有参构造函数调用" << endl;
}
person(const person& a)
{
cout << "person的拷贝构造函数调用" << endl;
}
~person()
{
cout << "person的析构函数调用" << endl;
}
};
void test()
{
person p1(10);
person p2(p1);
}
int main()
{
test();
system("pause");
return 0;
}
第一个问题:为什么必须是当前类的引用呢?
如果拷贝构造函数的参数不是当前类的引用,而是当前类的对象,那么在调用拷贝构造函数时,会将另外一个对象直接传递给形参,这本身就是一次拷贝,会再次调用拷贝构造函数,然后又将一个对象直接传递给了形参,将继续调用拷贝构造函数……这个过程会一直持续下去,没有尽头,陷入死循环。
只有当参数是当前类的引用时,才不会导致再次调用拷贝构造函数,这不仅是逻辑上的要求,也是 C++ 语法的要求。
第二个问题: 为什么是 const 引用呢?
拷贝构造函数的目的是用其它对象的数据来初始化当前对象,并没有期望更改其它对象的数据,添加 const 限制后,这个含义更加明确了。
另外一个原因是,添加 const 限制后,可以将 const 对象和非 const 对象传递给形参了,因为非 const 类型可以转换为 const 类型。如果没有 const 限制,就不能将 const 对象传递给形参,因为 const 类型不能转换为非 const 类型,这就意味着,不能使用 const 对象来初始化当前对象了。
如果将拷贝构造函数的形参的类型修改,去掉const,则不能对const类型的对象进行拷贝,如果同时存在拷贝构造函数的形参既有const引用,又有普通的引用,即拷贝构造函数发生重载,则可以对const和非const类型对象进行拷贝操作,如下代码,但我们一般只需要定义形参是const引用类型的拷贝构造函数即可。
#include <iostream>
using namespace std;
#include <string>
class person
{
public:
person()
{
cout << "person的无参构造函数调用" << endl;
}
person(int a)
{
cout << "person的有参构造函数调用" << endl;
}
person(person& a)
{
cout << "person的拷贝构造函数调用" << endl;
}
person(const person& a)
{
cout << "person的拷贝构造函数调用" << endl;
}
~person()
{
cout << "person的析构函数调用" << endl;
}
};
void test()
{
const person p1(10);
person p2(p1);
person p3(20);
person p4(p3);
}
int main()
{
test();
system("pause");
return 0;
}