C++ 面向对象编程2
析构函数
1.概念
析构函数是一个特殊的函数,函数名和类名相同,但是前面要加一个~,不能有参数,也没有返回值,析构函数在对象被销毁时自动调用一次,如果类中没有析构函数,编译器会自动生成一个什么也不做的析构函数;
2.用法
在销毁对象时需要释放资源,比如动态内存,硬件设备...;在构造函数中申请了动态内存(new),在析构函数中使用(delete);
3.调用时机
1.对象生命周期结束,被销毁时;
2.主动调用delete;
3.对象a是对象b的成员,b的析构函数被调用时,对象a的析构函数也被调用。
拷贝构造函数
1.概念
拷贝构造函数是一个特殊的构造函数,当使用已有的对象去初始化一个新建对象时会调用拷贝构造函数。
A a;//构造对象
A b = a;//会自动调用对象中的拷贝构造函数
2.语法
class A{
public:
A(){}//构造函数
/*拷贝构造*/
A(const A &a){
...... //将旧对象中的数据拷贝到新对象
}
};
类中会有一个默认的拷贝构造函数,但是只是进行逐字节拷贝,这样当需要拷贝的是一个指针时,新建的对象和旧的对象中的指针成员都会指向同一个内存地址,这样便导致了这两个对象会相互影响(浅拷贝)。如果希望自定义拷贝过程时重写拷贝构造,其实就是对象内部存在独立内存需要重写拷贝构造(深拷贝),如下图所示:
3.实验程序
#include <iostream>
#include <cstring>
using namespace std;
class A{
public:
A(int num, const char *str)
{
this->num = num;
this->str = new char[strlen(str) + 1];
strcpy(this->str, str);
}
//拷贝构造函数
A(const A& old)
{
this->num = old.num;
this->str = new char[strlen(old.str) + 1];
strcpy(this->str, old.str);
}
void print()
{
cout<<"num:"<<this->num<<"str:"<<str<<endl;
}
void modify_data(int num, const char *str)
{
this->num = num;
strcpy(this->str, str);
}
//因为使用了堆空间,所以当对象的生命周期结束后应当使用析构函数将所占用的空间释放
~A()
{
cout<<"~A"<<endl;
delete[] str;
}
private:
int num;
char *str;
};
int main()
{
A a1(1, "hello");
A a2 = a1;
a1.print();
a2.print();
a1.modify_data(2, "world");
a1.print();
a2.print();
a2.modify_data(3, "world");
a1.print();
a2.print();
return 0;
}
输出结果:
可以看出使用深度拷贝两个对象的指针成员所指向的数据不会相互影响;因为创建了两个对象,所以当对象的生命周期结束后系统会自动调用各自的析构函数,所以打印了两次~A。
类中的特殊成员
1.const对象和const成员函数
1).语法
class A{
public:
....
....
void show()const{......}//const成员函数
private:
const int num;//const成员变量
mutable int num_1;//const函数可以修改
...
...
};
A a;
const A b;//const对象
a.const成员函数可以和同名的普通成员函数构成重载关系:非const对象优先调用非const函数,如果没有非const成员函数,再去调用const成员函数;
b.const对象只能调用const函数,不能调用非const函数;
c.const成员函数只能读取成员变量,不能修改成员变量,如果一定要修改,声明成员变量前要加mutable。
1).实验程序
#include <iostream>
using namespace std;
class A{
public:
A(int n,int m):num(n),x(m)
{
cout<<"A(int,int)"<<endl;
}
void show()
{
cout<<"show()"<<endl;
}
//const成员函数
void show()const
{
x = 100;
cout<<x<<endl;
cout<<"show()const"<<endl;
}
private:
const int num;//const成员变量
mutable int x;//允许在const函数中修改
};
int main()
{
A a(10,15);
const A b(44,55);
a.show();
b.show();
return 0;
}
输出结果:
2.静态(static)成员
静态成员分为静态成员变量和静态成员函数。
a.静态成员变量
静态成员变量声明时加static,必须初始化,而且必须在类外初始化。默认初始化为0,类类型的静态成员调用默认的构造函数。
b.静态成员函数
静态成员函数声明时加static,静态成员函数只能访问静态成员,不能访问非静态成员。
1).实验程序
#include <iostream>
using namespace std;
class A{
public:
int x;
static int num;//静态成员变量
void show()
{
cout<<"show()"<<endl;
}
//静态成员函数
static void show1()
{
A::num = 50;
cout<<"static show()"<<endl;
}
};
//静态成员变量必须在类外初始化
int A::num = 111;
int main()
{
A::num = 100;
cout<<A::num<<endl;
A::show1();
A a;
a.show1();
a.num = 60;
A b;
cout<<b.num<<endl;
return 0;
}
输出结果:
从以上的实验程序可以看出,静态成员不需要通过对象访问,可以直接通过类名访问,静态成员属于类,而不属于某个对象,所以静态成员是被共享的。静态成员具有以上性质的原因时它们存放在独立的内存中。不要在静态成员函数中使用this指针。
3.友元
一般类中的数据都是私有属性的,友元的作用就是让类外的数据突破访问权限的限制,友元可以访问类内任意数据。可以将 类/函数 声明为某个类的友元,友元类和友元函数就可以访问类中的数据。
1).友元函数
友元函数是一个全局函数,在类内部将该函数声明为友元,这个全局函数就可以访问类内部的数据,语法:
在类内部声明:
friend 函数声明;
2).友元类
如果类A声明为类B的友元类,那么类A中就可以随意访问类B中的所有数据,不受访问权限的限制,语法:
在类内部声明:
friend class 类名;
3).实验程序
#include <iostream>
using namespace std;
class A{
public:
int x;
void showA()
{
cout<<x<<":"<<y<<":"<<z<<endl;
}
protected:
int y;
private:
int z;
//友元函数声明
friend void setA(A &a);
//友元类声明
friend class B;
};
class B{
public:
void show()
{
cout<<a.x<<":"<<a.y<<":"<<a.z<<endl;
}
void setA()
{
a.x = 40;
a.y = 50;
a.z = 60;
}
private:
A a;
};
void setA(A &a)
{
a.x = 10;
a.y = 20;
a.z = 30;
}
int main()
{
A a;
setA(a);
a.showA();
B b;
b.setA();
b.show();
return 0;
}
输出结果:
总结
1.友元不受访问属性的限制,可以类外访问类中的所有数据,破坏了类的封装特性,如非必要,不要使用;