1深复制和浅复制
在使用一个对象对另一个对象初始化或赋值时,复制构造函数能对对象的属性进行复制。但如果存在指针成员,则指针的值会被复制,导致两个对象的指针成员指向同一块内存,在释放的时候对象的时候,会重复释放,导致程序崩溃。若对象包含指针成员变量,则需要手动的编写拷贝构造函数实现深拷贝,调用编译器的内部默认的拷贝构造函数则只能实现浅拷贝操作。
深拷贝:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
class student{
public:
student(int age,char *name){
this->age=age;
this->name=new char[10];
strcpy(this->name,name);
}
student(const student& s){
this->age=s.age;
this->name=new char[10];
strcpy(this->name,name);
}
~student(){
delete this->name;
}
void show(){
printf("Student[addr=%x, age=%d, age.addr=%x, name=%s, name.addr=%x, name.value=%x]\n", this, age, &age, name, &name, name);
}
private:
int age;
char* name;
};
int main(){
student s1(20,"小明");
s1.show();
student s2=s1;
s2.show();
return 0;
}
调用的是拷贝构造函数还是赋值运算符,主要是看是否有新的对象实例产生,如果产生了新的对象实例,那调用的就是拷贝构造函数;如果没有,那就是对已有的对象赋值,调用的是赋值运算符。
2.静态成员变量:
有时候我们希望在多个对象之间共享数据,对象a改变了某份数据后对象b可以检测到。
典型例子就是 计数
例子:以student为例,如果我们想知道班级中共多少名学生,就可以设置一份共享的变量,每次创建对象时让该变量加1。
没有在类外初始化的static成员变量是不能使用的
#include<iostream>
using namespace std;
class student
{
public:
student(char* name,int age,float score):m_name(name),m_age(age),m_score(score){
m_total++;
}
void show(){
cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<"(当前共有"<<m_total<<"名学生)"<<endl;
}
private:
int m_age;
float m_score;
char* m_name;
static int m_total;
};
int student::m_total=0;
int main(){
student stu((char*)"小张",15,90);
stu.show();
(new student((char*)"小明",15,90))->show();
(new student((char*)"小王",16,92))->show();
(new student((char*)"小李",12,88))->show();
(new student((char*)"小华",18,92))->show();
}
输出:
小张的年龄是15,成绩是90(当前共有1名学生)
小明的年龄是15,成绩是90(当前共有2名学生)
小王的年龄是16,成绩是92(当前共有3名学生)
小李的年龄是12,成绩是88(当前共有4名学生)
小华的年龄是18,成绩是92(当前共有5名学生)
3.静态成员函数
普通成员函数可以访问所有成员(包括成员变量和成员函数),静态成员可以调用静态成员函数。
#include<iostream>
using namespace std;
class student
{
public:
student(char* name,int age,float score):m_name(name),m_age(age),m_score(score)
{
m_total++;
m_point+=score;
}
void show(){
cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
}
static int gettotal()//静态成员函数
{
return m_total;
}
static float getpoint()//静态成员函数
{
return m_point;
}
private:
static int m_total;
static float m_point;
char *m_name;
int m_age;
float m_score;
};
int student::m_total=0;
float student::m_point=0.0;
int main(){
(new student((char*)"小明",15,90))->show();
(new student((char*)"小王",16,92))->show();
(new student((char*)"小李",12,88))->show();
(new student((char*)"小华",18,92))->show();
int total=student::gettotal();
float points=student::getpoint();
cout<<"当前共有"<<total<<"名学生,总成绩"<<points<<",平均分是"<<points/total<<endl;
return 0;
}
输出:
小明的年龄是15,成绩是90
小王的年龄是16,成绩是92
小李的年龄是12,成绩是88
小华的年龄是18,成绩是92
当前共有4名学生,总成绩362,平均分是90.5
4.const修饰成员函数
const成员函数可以使用类中的所有成员变量,但是不能修改他们的值,这种措施主要还是为了保护数据而设置的。const成员的函数也称为常成员函数。
#include<iostream>
using namespace std;
class student
{
public:
student(char* name,int age,float score):m_name(name),m_age(age),m_score(score){}
void show()
{
cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
}
char* getname() const;
int getage() const;
float getscore() const;
private:
char* m_name;
int m_age;
float m_score;
};
char* student::getname()const
{
return m_name;
}
int student::getage()const
{
return m_age;
}
float student::getscore()const
{
return m_score;
}
getname(),getage(),getscore()三个函数的功能都很简单,仅仅是为了获取成员变量的值,没有任何修改成员变量的企图,所以我们加了const 限制,这是一种保险的做法,同时也便得语义更加明显。
需要注意的是,必须在成员函数的声明和定义处同时加上const关键字。char * getname() const和char *getname()是两个不同的函数原型,如果只在一个地方加const会导致声明和定义处的函数原型发生冲突。
5.前向引用申明
class B;//没有这句会报错
class A{
public:
void f(B b);
};
class B{
public:
void f(A a);
};
经过类的前向引用申明,可以声明该类的对象引用或指针。但是在提供一个类的完整定义之前,不能定义该类的对象,也不能在内联函数中使用该类的对象。
6.位域
有些信息在存储时,并不需要占用一个完整的字节,而只需要占用一个或几个二进制位。
长度最小的char和bool在内存中都占据一个字节的空间。c++允许在类中声明位域。位域是一种允许将类中的多个数据成员打包,从而使不同的成员可以共享相同的字节的机制。在类中的定义位域的方式为:
数据类型说明符 成员名 :位数;
位数指定一个位域所占用的二进制位数。使用位域:
(1)C++并未规定打包的具体方式,因此不同的编译器会有不同的方式,不同编译器下,包含位域的类所占用的空间也不同。
(2)只有bool, char,int, enum的成员才能够被定义为位域。
(3)位域虽然节省了内存空间,但由于打包和解包过程中需要耗费额外的操作,所以运行时间很可能会增加。
#include<iostream>
using namespace std;
enum Level {FRESHMAN, SOPHOMORE, JUNIOR, SENIOR};
enum Grade {A, B, C, D};
class Student {
public:
Student (unsigned number, Level level, Grade grade) : number(number), level(level), grade(grade) {}
private:
unsigned number : 27;
Level level : 2; //类型一定要为unsigned,否则如果最高位为1,会被解释为负数
Grade grade : 2;
};
int main(){
Student s (123456578, SOPHOMORE, B);
cout << sizeof(s) << endl;
}
输出 4