打算做一个C++知识体系的专栏,干货较多,整理较慢,会定期产出,想学习可以关注一下。
本文承接上篇文章面向对象之微观部分——类的组成(上)感兴趣的可以了解一下。
常对象: 就是const修饰的类对象,常引用也是常对象。
const + 类型(引用类型) 对象名(构造函数的实参表);
类型(引用类型) const + 对象名(构造函数的实参表);
常对象:只能以读取的方式访问类中的属性,而不能修改类中的属性。
起到作用:保护与安全机制。
注:
常函数: 就是const修饰的类中的成员函数就叫常函数。
class 类名
{
//类中的成员函数 + const修饰
返回值 函数名(形参列表)const
{
//常成员函数,简称常函数。
}
};
注:
this
指针本来应该是 : 类名 * const this;
this
指针应该是:const 类名 * const this;
常函数的特点是:
在常函数中只能以读取类中属性的方式进行访问,而不能对类中的属性进行写操作。
常函数的意义:
保护类中的属性或形参变量不会在常函数体内被修改,起到一种保护作用。
mutable关键字:mutable
关键字修饰的成员变量允许在常成员函数中修改;
代码示例:
#include
using namespace std;
class Student
{
private:
string name;
int age;
mutable int id;//允许在常函数中修改
public:
Student(string _name,int _age,int _id):name(_name),age(_age),id(_id)
{
}
void show()const
{
cout << "常成员函数" << endl;
//常成员函数不能修改成员变量的值
//this->age=100;错误的
this->id=1003;//可以修改因为有mutable修饰
cout << "姓名:" << name << " 年龄:" << age <<" 学号:" << id << endl;
}
//与上面构成重载关系
void show()
{
cout << "普通成员函数" << endl;
cout << "姓名:" << name << " 年龄:" << age <<" 学号:" << id << endl;
}
};
int main()
{
Student s1("张三",20,1001);
s1.show();
const Student s2("夜猫徐",20,1002);
s2.show();
const Student& a=s1;
a.show();//通过引用访问成员函数
return 0;
}
mutable
关键字修饰的成员变量允许在常成员函数中修改。语法关系:
常对象,只能调用常函数,而不能调用普通函数。因为普能函数有写的属性。
普通对象,当然可以调常函数。
代码验证:
#include
using namespace std;
class Stu
{
private:
string name;
int age;
public:
Stu(string _name,int _age):name(_name),age(_age)
{
cout << "Stu中的有参构造" << endl;
}
~Stu()
{
cout << "Stu的析构" << endl;
}
int getage()const//常函数
{
//this->age=100;这种方式报错
//常函数是不能修改类中的属性的
return this->age;
}
string getname()const
{
//this->name="lisi";
return this->name;
}
};
bool compare(const Stu& stu1,const Stu& stu2)
{
//常对象只能调用常函数。
return stu1.getage()>stu2.getage();
}
int main()
{
Stu stu1("zhangsan",20);
Stu stu2("yemaoxu",18);
if(compare(stu1,stu2))
{
cout << "zhangsan大" << endl;
}
else
{
cout << "yemaoxu大" << endl;
}
return 0;
}
常对象与常函数在程序设计中的意义:
const
修饰的常对象及常函数起到了对,对象中的数据的保护,使大型工程代码更健壮更安全。
我们知道static
修饰的成员对象,是对成员对象存储形式的修饰,他存储在静态区。
必须在类中声明,类外初始化,才能定义空间。
所以他具有以下特征:
::
域名访问符直接访问。加了static
修饰的成员函数,他不并是修饰函数的存储形式,是修饰的他的函数级别。
由static
修饰的成员函数变成了一个全局函数,只不过是隐藏在类的作用域之中而已。
当这个成员函数被升级为全局函数之后,类中对象在堆上或在栈上什么时候产生就与之没有关系了,因为它没有了this指针。
且因为静态成员函数没有了this指针,所以无法访问类中非静态的属性,只能访问类中的静态属性。
所以静态成员函数也具有静态成员变量的特性,他不依赖于某个对象的调用,他是为整个类而服务,而非某个对象。
代码示例:
#include
using namespace std;
class Stu
{
private:
string name;
int age;
public:
static int count;
Stu(string name, int age)
{
this->name = name;
this->age = age;
count++;
}
static void getCount()
{
//cout << name <
cout << count <<endl;//只能访问静态成员变量
}
};
//类中静态成员变量类外进行初始化的方式:
int Stu::count;
int main()
{
Stu stu1("yemaoxu",18);
Stu stu2("zhangsan",20);
stu1.getCount();//访问静态成员函数的方式1:通过类对象访问
Stu::getCount();//访问静态成员函数的方式2:通过类名直接访问
//cout << Stu::count << endl;//私有属性时不可以访问,公有时可以通过类对象访问
cout << stu1.count << endl;//静态成员变量的访问方式1:在静态成员变量是公有时可以通过类对象访问
Stu::count=5;//静态成员变量的访问方式2:在静态成员变量是公有时通过类名直接访问
cout << Stu::count << endl;
stu1.count=10;//通过stu1修改count,通过stu2访问也会发生变化
cout << stu2.count << endl;
return 0;
}
int Stu::静态变量名;
为什么要使用静态成员变量?
为了让成员变量的存在不依赖于任何类对象。
为什么要使用静态成员函数?
为了让函数逻辑的执行不依赖于任何类对象。
函数名:与类同名
返回值:没有
形参表:const 类名 &
类名(const 类名& other)
{
this->属性 = other.属性;
...
}
编译器会默认使用浅拷贝,第一性能方面浅拷贝性能更高,第二,编译器没有考虑到类中有属性指针指向堆区的情况。
如果有类中属性指针指向堆区,那么就必须进行深拷贝的改造。
代码示例:
#include
using namespace std;
class Stu
{
private:
string name;
int age;
public:
//有参构造
Stu(string name, int age)
{
this->name = name;
this->age = age;
}
//编译器给我提供默认拷贝构造语法形式:这种简单的值的传递也称之为浅拷贝。浅拷贝对计算机性能消耗较低。
Stu(const Stu& other):name(other.name),age(other.age)//拷贝构造函数也可以有初始化表
{
//this->name=other.name;
//this->age=other.age;
}
void show()
{
cout << "姓名:" << name << " 年龄:" << age << endl;
}
};
int main()
{
Stu s1("夜猫徐",18);
s1.show();
//显式调用类中拷贝构造,当类中并没有提供这个构造,编译器会自动给生成一个拷贝构造.
//这个编译器会自动给生成的拷贝构造就是上面我们写的拷贝构造语法形式实现的。
Stu s2(s1);
s2.show();
//隐式调用类中的拷贝构造。
Stu s3 = s1;
s3.show();
Stu *p=new Stu(s1);
p->show();
delete p;
p=nullptr;
return 0;
}
}
Stu s3 = s1;
Stu s2(s1);
有类中属性指针指向堆区,那么就必须进行深拷贝的改造。
改造代码示例:
#include
using namespace std;
class Stu
{
private:
string name;
int age;
int *p;
public:
//有参构造
Stu(string name, int age)
{
this->name = name;
this->age = age;
this->p=new int[20];//有类中属性指针指向堆区,升级为深拷贝
cout << "Stu的构造" << endl;
}
//编译器给我提供默认拷贝构造语法形式:这种简单的值的传递也称之为浅拷贝。浅拷贝对计算机性能消耗较低。
Stu(const Stu& other)/*:name(other.name),age(other.age)*/
{
this->name=other.name;
this->age=other.age;
//指针成员也会只做简单的赋值,相当于两个对象的指针成员指向的是同一块空间,调用析构函数释放时,
//就会出现 double free的问题不能简单的使用浅拷贝,要升级为深拷贝
//深拷贝实现步骤
//1.开辟新空间
this->p=new int[20];
//2.拷贝数据
//memcpy(this->p, other.p,sizeof(int[20]));
//memove针对小型嵌入设备的开发。
memmove(this->p,other.p,sizeof(int[20]));//memmove有一种安全机制。
cout << "Stu的拷贝构造" << endl;
}
~Stu()
{
delete []p;
cout << "Stu的析构" << endl;
}
void show()
{
cout << "姓名:" << name << " 年龄:" << age << endl;
}
};
int main()
{
Stu s1("夜猫徐",18);
s1.show();
//显式调用类中拷贝构造,当类中并没有提供这个构造,编译器会自动给生成一个拷贝构造.
//这个编译器会自动给生成的拷贝构造就是上面我们写的拷贝构造语法形式实现的。
Stu s2(s1);
s2.show();
//隐式调用类中的拷贝构造。
Stu s3 = s1;
s3.show();
return 0;
}
double free
的错误。以下面代码为负面例子:
#include
using namespace std;
class Stu
{
private:
string name;
int age;
int *p;
public:
//有参构造
Stu(string name, int age)
{
this->name = name;
this->age = age;
this->p=new int[20];//有类中属性指针指向堆区,升级为深拷贝
cout << "Stu的构造" << endl;
}
//编译器给我提供默认拷贝构造语法形式:这种简单的值的传递也称之为浅拷贝。浅拷贝对计算机性能消耗较低。
Stu(const Stu& other)/*:name(other.name),age(other.age)*/
{
this->name=other.name;
this->age=other.age;
//指针成员也会只做简单的赋值,相当于两个对象的指针成员指向的是同一块空间,调用析构函数释放时,
//就会出现 double free的问题不能简单的使用浅拷贝,要升级为深拷贝
//深拷贝实现步骤
//1.开辟新空间
this->p=new int[20];
//2.拷贝数据
//memcpy(this->p, other.p,sizeof(int[20]));
//memove针对小型嵌入设备的开发。
memmove(this->p,other.p,sizeof(int[20]));//memmove有一种安全机制。
cout << "Stu的拷贝构造" << endl;
}
~Stu()
{
delete []p;
cout << "Stu的析构" << endl;
}
void show()
{
cout << "姓名:" << name << " 年龄:" << age << endl;
}
int getage()
{
return this->age;
}
};
Stu compare_age(Stu stu1,Stu stu2)
{
return stu1.getage()>stu2.getage()?stu1:stu2;
}
int main()
{
Stu s1("夜猫徐",18);
s1.show();
Stu s4("zhangsan",20);
cout << "-------------------" << endl;
compare_age(s1,s4).show();
return 0;
}
优化后代码:
其他不遍,加上引用。
Stu& compare_age(Stu& stu1,Stu& stu2)
{
return stu1.getage()>stu2.getage()?stu1:stu2;
}
浅拷贝:
如果类中没有显性的定义拷贝构造函数,编译器会默认提供一个拷贝构造函数,这个默认提供的拷贝构造函数,只完成成员之间一对一的简单赋值,如果类中没有指针,使用这个默认的拷贝构造函数,是没有问题的。
深拷贝:
如果类中有指针成员,并且使用浅拷贝时,指针成员也会只做简单的赋值,相当于两个对象的指针成员指向的是同一块空间,调用析构函数释放时,就会出现 double free
的问题。所以需要在类中显性给定拷贝构造函数,并给新对象的指针成员重新分配空间,再将旧对象指针成员指向的空间里的数据复制一份过来。
图例:
匿名对象(也称临时对象)
匿名对象产生的情况:
使用匿名对象给对象赋值方式
类名 s=类名("小红",16);
/*在执行此代码时,利用构造函数生成了一个匿名类对象;然后将此匿名变成了s这个实例对象*/
类名();
/*在执行此代码时,利用无参构造函数生成了一个匿名类对象;执行完此行代码,
因为外部没有接此匿名对象的变量,此匿名又被析构了*/
匿名对象的生命周期
说明:
在执行类名( )时,生成了一个匿名对象,执行完后,此匿名对象就此消失。这就是匿名对象的生命周期。
在执行类名 s=类名(“小红”,16);时,首先生成了一个匿名对象,因为外部有s对象在等待被实例化,然后将此匿名对象变为了s对象,其生命周期就变成了s对象的生命周期。
总结:
如果生成的匿名对象在外部有对象等待被其实例化,此匿名对象的生命周期就变成了外部对象的生命周期;如果生成的匿名对象在外面没有对象等待被其实例化,此匿名对象将会生成之后,立马被析构。
代码示例:
#include
using namespace std;
class Student{
private:
string name;
int age;
public:
Student()
{
cout<<"无参构造函数"<<endl;
}
Student(string n, int a):name(n),age(a)
{
cout<<"有参构造函数"<<endl;
}
void show()
{
cout<<"姓名"<< name <<" 年龄"<< age << endl;
}
~Student()
{
cout<<"析构函数"<<endl;
}
};
void func(Student x)
{
x.show();
}
int main(){
Student();//匿名对象在外面没有对象等待被其实例化,此匿名对象将会生成之后,立马被析构。
cout<<"-------------------"<<endl;
Student s1("小明", 18);//有参构造
s1.show();
//使用匿名对象给s2赋值
Student s2=Student("小红",16);
cout<<"-------------------"<<endl;
//匿名对象一般多用于类数组的初始化
Student arr[5]={Student("a",10),Student("b",20)};
cout<<"-------------------"<<endl;
//或者用于给函数传参
func(Student("张三",35));
cout<<"-------------------"<<endl;
return 0;
}
总结:
匿名对象一般多用于类数组的初始化,或者用于给函数传参的。