类(class)
C++与C最大的一个区别就是,比C语言要多很多面向对象的特性,而对象的体现很多都是在类上。C语言中会把很多功能封装在一个函数中,而在C++中多了一个概念也就是类,虽然面向对象的执行效率不高,但是代码会比较整齐。要对面向对象有深入了解可以先从类下手。
如何定义一个类
class p{
/*代码体*/
};
类和结构体定义会比较类似,但是它们也是有区别的,首先是他们的访问默认权限,在类中如果不声明权限,那么默认是private私有的,结构体默认访问权限是public公有的,然后在C++中有规定,任何不同的对象不能拥有相同的内存地址,对于空类或空结构体他们的大小都为1。
封装
封装顾名思义就是把类中的成员遮蔽起来,不能让外界直接访问类中的成员,只能通过类中留下的访问接口,来访问或者修改类中的成员,这样的好处就是安全,能够最大限度的保证你的内部数据不会被外界轻易的修改,C++有这三种关键字来修饰访问权限,public、protected、private,外界对类访问的权限逐渐降低,所以尽量把类中的成员变量都用private修饰。
#include <iostream>
#include <string>
using namespace std;
class person{
public:
void getName(){
cout<<"name is"<<m_name<<endl;
}
void setName(string name){
m_name=name;
}
private:
int m_age;
string m_name;
};
int main(){
person p1;
p1.setName("张三");
p1.getName();
非法调用// cout<<p1.m_name<<endl;
return 0;
}
这里简单的实现一个封装,使实例的对象无法直接访问成员变量,否则会出错
构造函数
我们除了给变量初始化除了通过实例化对象赋值,还可以用其他方法赋值吗,当然是可以的,这里就用到了C++类中的构造函数,构造函数是怎么样一个结构呢,他是一个名字和类名相同,但是不需要加任何返回值类型的函数。
举例有一个类class p{
构造函数:p(){
}
};
既然是函数那肯定可以跟上参数的,当带上参数时,它就是带参构造函数,当程序员不在类中定义构造函数时,会默认生成一个无参的构造函数,构造函数可以根据自己的实际需求来写。
#include <iostream>
#include <string>
using namespace std;
class person{
public:
person(){
cout<<"这是无参构造方法"<<endl;
}
person(string name){
cout<<"这是带参构造方法"<<endl;
m_name=name;
}
void getName(){
cout<<m_name<<endl;
}
private:
int m_age;
string m_name;
};
int main(){
person p1("张三");
p1.getName();
person p2;
return 0;
}
这里可以看到,带参构造函数和无参构造函数是可以同时存在于类中的,调用取决于在实例化对象时是否传递了参数,这里需要注意当调用无参构造函数时,不需要加(),否则会被编译器当成普通函数的声明,除了直接在括号内像普通函数一样传参,还可以用一种方法传参,它就是初始化列表,顾名思义就是用来做初始化用的。如下
#include <iostream>
#include <string>
using namespace std;
class person{
public:
person(string name,int age):m_name(name),m_age(age){
cout<<"这是带参构造方法"<<endl;
}
void getName(){
cout<<m_name<<"的岁数是:"<<m_age<<endl;
}
private:
int m_age;
string m_name;
};
int main(){
person p1("张三",36);
p1.getName();
person p2;
return 0;
}
析构函数
除了默认有构造函数外还会有一个析构函数,析构函数是在某个实例化对象结束运行之后,用来释放空间用的,并且不可以有参数,不可以发生重载。
格式是类person{
析构函数:~person(){
}
}
#include <iostream>
#include <string>
using namespace std;
class person{
public:
person(){
cout<<"这是无参构造方法"<<endl;
}
person(string name,int age):m_name(name),m_age(age){
cout<<"这是带参构造方法"<<endl;
}
~person(){
cout<<"这是析构函数"<<endl;
}
void getName(){
cout<<m_name<<"的岁数是:"<<m_age<<endl;
}
private:
int m_age;
string m_name;
};
int main(){
person p1("张三",36);
p1.getName();
person p2(p1);
p2.getName();
return 0;
}
可以看到析构函数都是最后才运行,所以作用就是用来释放对象的内存空间的
拷贝构造函数
除了默认有一个无参构造函数外,第三个会有一个默认的拷贝构造函数,它的作用是当实例化好一个一个对象后,把这个对象当作参数传递给拷贝构造函数,被拷贝的对象会把所有对象成员赋值一遍给目的对象。
#include <iostream>
#include <string>
using namespace std;
class person{
public:
person(){
cout<<"这是无参构造方法"<<endl;
}
person(string name,int age):m_name(name),m_age(age){
cout<<"这是带参构造方法"<<endl;
}
/*person(const person &p){
cout<<"这是拷贝构造函数"<<endl;
m_name=p.m_name;
m_age=20;
}*/
void getName(){
cout<<m_name<<"的岁数是:"<<m_age<<endl;
}
private:
int m_age;
string m_name;
};
int main(){
person p1("张三",36);
p1.getName();
person p2(p1);
p2.getName();
return 0;
}
默认拷贝构造函数会全部拷贝,所以也可以自己写拷贝构造函数,如上
注释掉后
未注释
注意这里拷贝构造函数的参数是引用,而且必须加上const修饰
浅拷贝与深拷贝
浅拷贝是指拷贝函数直接在数值上直接拷贝,没有用到指针类型的参数,可以用到浅拷贝,若是用到了指针类型的,就可以用深拷贝,来重新申请一段空间,因为用到了指针类型的参数必定会遇到空间创建与释放的操作,那么如果只用了浅拷贝就会导致内存重复释放的危险。
浅拷贝(重复释放导致段错误)
#include <iostream>
#include <string>
using namespace std;
class person{
public:
person(){
cout<<"这是无参构造方法"<<endl;
}
person(string name,int age){
cout<<"这是带参构造方法"<<endl;
m_age=new int(age);
}
~person(){
if(m_age!=NULL){
delete m_age;
m_age=NULL;
}
cout<<"这是析构函数"<<endl;
}
person(const person &p){
cout<<"这是拷贝构造函数"<<endl;
m_name=p.m_name;
m_age=p.m_age;默认拷贝构造函数的做法
// m_age=new int (*p.m_age);
}
void getName(){
cout<<m_name<<"的岁数是:"<<*m_age<<endl;
}
private:
int *m_age;
string m_name;
};
int main(){
person p1("张三",36);
p1.getName();
person p2(p1);
p2.getName();
return 0;
深拷贝
#include <iostream>
#include <string>
using namespace std;
class person{
public:
person(){
cout<<"这是无参构造方法"<<endl;
}
person(string name,int age){
cout<<"这是带参构造方法"<<endl;
m_age=new int(age);
}
~person(){
if(m_age!=NULL){
delete m_age;
m_age=NULL;
}
cout<<"这是析构函数"<<endl;
}
person(const person &p){
cout<<"这是拷贝构造函数"<<endl;
m_name=p.m_name;
// m_age=p.m_age;默认拷贝构造函数的做法
m_age=new int (*p.m_age);
}
void getName(){
cout<<m_name<<"的岁数是:"<<*m_age<<endl;
}
private:
int *m_age;
string m_name;
};
int main(){
person p1("张三",36);
p1.getName();
person p2(p1);
p2.getName();
return 0;
这里可以思考一个问题,当一个类里有另一个类作为成员时,那么各自的构造函数和析构函数是谁先运行,谁后运行呢?我们可以实践一下。
#include <iostream>
using namespace std;
class student{
public:
student(){
cout<<"这是类1的构造函数"<<endl;
}
~student(){
cout<<"这是类1的析构函数"<<endl;
}
};
class person{
public:
person(){
cout<<"这是类2的构造函数"<<endl;
}
~person(){
cout<<"这是类2的析构函数"<<endl;
}
student s1;
};
int main(){
person p1;
return 0;
}
这里可以看到先运行被调用的类的构造函数,然后是本类的构造函数,然后是本类的析构函数,然后才是被调用的类的析构函数。