C++引用详情(引用的基本语法,注意事项,做函数的参数以及引用的本质,常量引用)
函数高级C++(函数的默认参数,函数的占位参数,函数重载的基本语法以及注意事项)
C++类和对象—封装(属性和行为作为整体,设计学生类,访问权限,class和struct的区别,成员属性私有化,设计案例(1立方体类,2点和圆的关系(多文件编程)))
C++类和对象—对象特征(构造函数与析构函数,函数的分类以及调用,拷贝构造函数调用时机,构造函数调用规则,深拷贝和浅拷贝,初始化列表,类对象作为类成员,静态成员)
C++对象模型和this指针(成员变量和成员函数分开存储,this指针的用途,空指针访问成员函数,const修饰成员函数)
C++类和对象-友元(全局函数做友元,友元类,成员函数做友元)
C++ 模板学习01(函数模版)(函数模板的语法、函数模板注意事项、函数模板案例-数组排序、普通函数与函数模板的区别、普通函数与函数模板的调用规则、模板的局限性)
C++模板学习02(类模板)(类模板语法、类模板与函数模板的区别、类模板中的成员函数创建时机、类模板对象做函数参数、类模板与继承、类模板成员函数类外实现、类模板分文件编写、类模板与友元)
对象的初始化和清理
生活中我们买的电子产品都基本会有出厂在某一天我们不用时候也会删除一些自己信息数据保证安全
C++中的面向对象来源于生活,每个对象也都会有初始设置以及 对象销毁前的清理数据的设置。
一.构造函数和析构函数
对象的初始化和清理也是两个非常重要的安全问题
一个对象或者变量没有初始状态,对其使用后果是未知
同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
C++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现。
构造函数: 主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
构造函数语法: 类名(){}
构造函数,没有返回值也不写void。
函数名称与类名相同。
构造函数可以有参数,因此可以发生重载。
程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次。
析构函数语法: ~类名(){}
1.析构函数,没有返回值也不写void。
2.函数名称与类名相同,在名称前加上符号~。
3.析构函数不可以有参数,因此不可以发生重载。
4.程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次。
#include<iostream>
using namespace std;
class person
{
public:
//构造函数
/* 1. 构造函数,没有返回值也不写void。
2. 函数名称与类名相同。
3. 构造函数可以有参数,因此可以发生重载。
4. 程序在调用对象时候会自动调用构造,无须手动调用, 而且只会调用一次。*/
person()
{
cout << "person 构造函数的调用" << endl;
}
/*析构函数
1.析构函数,没有返回值也不写void。
2.函数名称与类名相同, 在名称前加上符号~。
3.析构函数不可以有参数,因此不可以发生重载。
4.程序在对象销毁前会自动调用析构,无须手动调用, 而且只会调用一次。*/
~person()
{
cout << "person 析构函数的调用" << endl;
}
};
void test()//在栈上的数据,test执行完毕后,释放这个对象
{
person p;
}
int main()
{
test();
person p;
system("pause");
}
前两个是执行栈上的对象的结果,可以发现栈上的数据是使用完函数之后就会自动释放的,但是在主函数中的对象因为程序还没有执行完,还没有释放,所以也就没有执行析构函数。
程序结束完后,可以发现主函数中的对象p也释放了,并且执行了析构函数。
二.构造函数的分类及调用
两种分类方式:
按参数分为:有参构造和无参构造
按类型分为:普通构造和拷贝构造
三种调用方式:
括号法
显示法
隐式转化法
三种构造函数调用方式 如代码所示:
#include<iostream>
using namespace std;
class person
{
public:
person()
{
cout << "person的无参构造函数调用" << endl;
}
person(int a)
{
cout << "person的有参构造函数调用" << endl;
age = a;
}
person( const person &p)
{
cout << "person的拷贝构造函数调用" << endl;
age = p.age;
}//拷贝构造函数 将传入的人的属性拷贝到我身上
~person()
{
cout << "person的析构函数调用" << endl;
}
int age;
};
//调用
void test()
{
//括号法
person p1;//默认构造函数调用
person p2(10);//有参构造函数调用
person p3(p2);//拷贝构造函数调用
//注意事项
//调用默认构造函数时不要加() 也就是不可以person(); 这样用编译器会认为这是一个函数的声明不会认为是在创造对象
//显示法
person p4;
person p5 = person(10);
person p6 = person(p2);
//person(10);匿名对象(无名) 当前行执行结束后系统会自动收回匿名对象
//隐式转化法
person p7 = 10;
person p8 = p7;
}
int main()
{
test();
system("pause");
}
其中在使用匿名对象的时候要注意,执行完当前行结束后系统会自动收回匿名对象。
注意事项
调用默认构造函数时不要加() 也就是不可以person(); 这样用编译器会认为这是一个函数的声明不会认为是在创造对象。
拷贝构造函数调用时机
C++中拷贝构造函数调用时机通常有三种情况
使用一个已经创建完毕的对象来初始化一个新对象
值传递的方式给函数参数传值
以值方式返回局部对象
#include<iostream>
using namespace std;
class person
{
public:
person()
{
cout << "person的默认构造函数" << endl;
}//默认构造函数
person(int age)
{
m_age = age;
cout << "person的有参构造函数" << endl;
}//有参构造函数
person(const person&p)
{
m_age = p.m_age;
cout << "person的拷贝构造函数" << endl;
}//拷贝构造函数
~person()
{
cout << "person的析构函数调用" << endl;
}
int m_age;
};
//拷贝构造函数调用时机
//使用一个已经创建完毕的对象来初始化一个新对象
void test1()
{
person p1(20);
person p2(p1);
}
//值传递的方式给函数参数传值
void dowork(person p)
{
}
void test2()
{
person p;
dowork(p);
}
//以值方式返回局部对象
person dowork2()
{
person p1;
return p1;
}
void test3()
{
person p = dowork2();
}
int main()
{
test1();
test2();
test3();
system("pause");
}
四.构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
如果用户定义拷贝构造函数,c++不会再提供其他构造函数
五.深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
在C++中对于对象在使用拷贝函数的时候,系统会自动生成一个可以使用的拷贝构造函数,也就是默认一般都是浅拷贝。
深拷贝是在对于申请堆区进行手动删除的时候,需要自己写拷贝构造函数,为了防止多次删除相同,详见代码。
#include<iostream>
using namespace std;
class person
{
public:
person()
{
cout << "person的默认构造函数调用" << endl;
}
person(int age,int height)
{
m_age = age;
h_height = new int(height);
cout << "person的有参构造函数调用" << endl;
}
~person()
{
//析构代码,将堆区开辟数据做释放操作
if (h_height != NULL)
{
delete h_height;
h_height = NULL;
}
cout << "析构函数调用" << endl;
}
int m_age;
int* h_height;
};
void test1()
{
person p1(18,160);
cout << "p1的年龄为:" << p1.m_age<<"身高为:"<<*p1.h_height << endl;
person p2(p1);//编译器自动生成拷贝函数
cout << "p2的年龄为:" << p2.m_age <<"身高为:"<<*p2.h_height << endl;
}
int main()
{
test1();
system("pause");
}
这一段代码是使用编译器提供的拷贝构造函数,会做浅拷贝操作,代码有问题!(往下看)
如图所示是浅拷贝的结果两个对象的属性身高指针都是指向同一个堆区,现在问题就是,两个对象都是会执行析构函数的所以无论先执行那个对象的释放堆区,都会重复释放堆区而产生错误。
如图所示:
解决办法就是重写拷贝函数,使用深拷贝的办法,给每一个对象都重新申请一个自己的堆区,使用结束后保证就只释放自己指向的堆区。
如图所示:
深拷贝代码:
//自己实现拷贝函数 解决浅拷贝的问题
person(const person& p)
{
cout << "person 拷贝构造函数的调用" << endl;
m_age = p.m_age;
h_height = new int(*p.h_height);//深拷贝
}
解决问题后代码:
#include<iostream>
using namespace std;
class person
{
public:
person()
{
cout << "person的默认构造函数调用" << endl;
}
person(int age,int height)
{
m_age = age;
h_height = new int(height);
cout << "person的有参构造函数调用" << endl;
}
~person()
{
//析构代码,将堆区开辟数据做释放操作
if (h_height != NULL)
{
delete h_height;
h_height = NULL;
}
cout << "析构函数调用" << endl;
}
//自己实现拷贝函数 解决浅拷贝的问题
person(const person& p)
{
cout << "person 拷贝构造函数的调用" << endl;
m_age = p.m_age;
h_height = new int(*p.h_height);//深拷贝
}
int m_age;
int* h_height;
};
void test1()
{
person p1(18,160);
cout << "p1的年龄为:" << p1.m_age<<"身高为:"<<*p1.h_height << endl;
person p2(p1);//编译器自动生成拷贝函数
cout << "p2的年龄为:" << p2.m_age <<"身高为:"<<*p2.h_height << endl;
}
int main()
{
test1();
system("pause");
}
六.初始化列表
作业:
C++提供了初始化列表语法,用来初始化属性
语法:
构造函数():属性1(值1),属性2(值2)...{}
#include<iostream>
using namespace std;
class person
{
public:
//传统初始化
/*person(int a, int b, int c)
{
m_a = a;
m_b = b;
m_c = c;
}*/
//初始化列表初始化属性
person(int a,int b,int c) :m_a(a), m_b(b), m_c(c)
{
}
int m_a;
int m_b;
int m_c;
};
void test01()
{
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()
{
test01();
system("pause");
}
七.类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称为该成员为对象成员。
#include<iostream>
using namespace std;
#include<string>
class phone
{
public:
phone(string name)
{
m_pname = name;
}
string m_pname;
};
class person
{
public:
person(string name,string pname):m_name(name),m_phone(pname)
{
}
string m_name;
phone m_phone;
};
//当其他类对象作为本类成员,构造时候先构造类对象,再构造自身
//析构的顺序与构造的顺序相反
void test1()
{
person p("张三", "苹果max");
cout << p.m_name << "拿着:" << p.m_phone.m_pname << endl;
}
int main()
{
test1();
system("pause");
}
注意:
当其他类对象作为本类成员,构造时候先构造类对象,再构造自身
析构的顺序与构造的顺序相反
八.静态对象
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
静态成员变量
所有对象共享同一份数据
在编译阶段分配内存
类内声明,类外初始化
#include<iostream>
using namespace std;
#include<string>
//静态成员变量
class person
{
public:
/* 1. 所有对象共享同一份数据
2. 在编译阶段分配内存
3. 类内声明,类外初始化*/
static int m_a;
};
int person::m_a=100;
void test1()
{
person p;
cout << p.m_a << endl;
person p2;
p2.m_a = 200;
cout << p.m_a << endl;
}
void test2()
{
//静态成员变量不属于某个对象,所有对象都共享同一份数据
//因此静态成员变量有两种访问方式
//1通过对象进行访问
person p;
cout << p.m_a << endl;
//2通过类名进行访问
cout << person::m_a << endl;
}
int main()
{
test1();
test2();
system("pause");
}
注意:
静态成员变量要类内声明,在类外也要写一下,可以不初始化,初始默认值是0
静态成员变量是都可以访问的,也就是说p1可以p2也可以,而且是一个东西
静态成员函数
所有对象共享同一个函数
静态成员函数只能访问静态成员变量
#include<iostream>
using namespace std;
#include<string>
//• 静态成员函数
//1. 所有对象共享同一个函数
//2. 静态成员函数只能访问静态成员变量
class person
{
public:
static void func()
{
cout << "static void func函数的调用" << endl;
}
static int a;
};
int person::a = 10;
void test1()
{
//通过对象访问
person p;
p.func();
//通过类名来访问
person::func();
}
int main()
{
test1();
system("pause");
}