1、类的初始化
class A
{
public:
//1.在构造函数体内初始化
/*
A(int a, int b)
{
// 成员变量a 与 b 这里并没有被赋值,因为这里的a,b全部都是形参,把成员变量隐藏了
a = a;
b = b;
cout << "自定义有参构造函数!" << endl;
}
//解决方法一: 形参与成员变量名不同即可
A(int a1, int b1)
{
a = a1;
b = b1;
cout << "自定义有参构造函数!" << endl;
}
//解决方法二: 使用this指针
A(int a, int b)
{
this->a = a;//明确的指出是对象自己的a、b
this->b = b;
cout << "自定义有参构造函数!" << endl;
}
*/
//2.使用初始化列表进行成员变量的初始化
A(int a, int b) : a(a), b(b)
{
cout << "自定义有参构造函数!" << endl;
}
void print()
{
cout << a << " " << b << endl;
}
private:
int a; //按照此处的定义顺序初始化
int b;
};
这两种方式,对基本类型成员无区别的,但对复杂类型成员,如类和结构体等,则第二种效率高,因为第一种会调用一次默认的构造函数,在类构造函数里又会调用一次成员的构造函数再赋值,而第二种仅在初始化列表里调用一次成员的构造函数并赋值。
注:以下情况必须使用初始化列表:
- 在派生类中构造函数中初始化提供父类的初始化。
- 如果成员是常量或引用,成员无法使用赋值的方式,必须在初始化列表中初始化
- 成员变量是类的类型
2、常成员变量
常成员变量是一直不变的,不能被赋值的,并且构造函数中只能用初始化列表的方式,其他初始化方法都是非法的。
class A
{
public:
A(int a): a(a)//初始化列表
{
//this->a = a; //这种写法不能用于 const的成员变量,需用初始化列表
}
void print()
{
//a = 200; //成员函数中,无法修改const成员变量
cout << a << endl;
}
private:
const int a;//加上const、常成员变量
};
3、常成员函数
如果只想对对象进行读操作,则成员函数可以设计成常成员函数,在常成员函数中只能读取,而不能修改成员变量的值。
class B
{
public:
B(int b)
{
this->b = b;
}
//希望print中无法改变任何成员变量的值,则把他声明为常成员函数
void print() const
{
//this->b = 200; 不能进行任何修改成员变量的值,哪怕b可以修改
cout << this->b << endl;
}
//普通成员函数
void print2()
{
this->b = 200;
cout << this->b << endl;
}
private:
int b;
};
4、mutable
在C++中,mutable是为了突破const的限制而设置的。被mutable修饰的变量,将永远可变,即使在一个const函数中。
class B
{
public:
B(int b)
{
this->b = b;
}
void print() const
{
//this->a = 100;//不可以,在常成员函数中,无法修改
this->b = 200; //加上了mutable,使得在常成员函数中变成例外
cout << this->b << endl;
}
private:
int a;
mutable int b;
};
5、常对象
常对象保护了对象中所有的数据成员不能被任何函数访问和修改,只能使用常成员函数来访问。
class A
{
public:
A(int a)
{
this->a = a;
}
//普通成员函数
void print1()
{
this->a = 200;
cout << this->a << endl;
}
//常成员函数
void print2() const
{
//this->a = 200;//常成员函数中不可修改任何成员变量的值
cout << this->a << endl;
}
private:
int a;
};
int main()
{
const A a(111);// a是常对象,只能调用常成员函数
a.print2();
//a.print1();//不能调用非常成员函数
return 0;
}
6、拷贝构造函数
拷贝构造函数也是一种构造函数。它用同类型的对象来初始化新创建的对象。其唯一的形参是 const 类型 & ,此函数也由系统自动调用。
注:拷贝构造函数可以理解为就是构造函数的一种重载。
class A
{
public:
A(int a)
{
this->a = a;
cout << "调用构造函数!" << endl;
}
//固定写法,可以认为是构造函数的重载
A(const A & tmp)
{
this->a = tmp.a;
cout << "调用拷贝构造函数!" << endl;
}
void print()
{
cout << a << endl;
}
private:
int a;
};
int main()
{
A tmp1(111); //调用有参的构造函数
A tmp2(tmp1); //定义的时候用对象初始化,调用拷贝构造函数
A tmp3 = tmp1; //定义的时候用对象初始化,调用拷贝构造函数
//三个对象的成员变量a的值一样
tmp1.print();
tmp2.print();
tmp3.print();
return 0;
}
7、浅拷贝和深拷贝
浅拷贝只是简单的值拷贝,而深拷贝做的工作要更多
注:默认的拷贝构造函数是浅拷贝
class A
{
public:
A(char *str)
{
int len = strlen(str) + 1;
p = new char[len];
memcpy(p, str, len);
}
void print()
{
cout << p << endl;
}
//自定义的拷贝构造函数- 深拷贝
A(const A & tmp)
{
int len = strlen(tmp.p) + 1;
p = new char[len];
memcpy(p, tmp.p, len);
}
char *p; //指针变量
};
int main()
{
A a("hello");
a.print();
//浅拷贝:调用默认的拷贝构造函数,仅仅值拷贝,相当于 b.p= a.p;
//深拷贝:使用自定义的拷贝构造函数,相当于进行了内存的拷贝
A b(a);
b.print();
strcpy(a.p, "world");
a.print();
b.print();
return 0;
}
8、析构函数
析构函数与构造函数相反,它是特殊的类成员函数,它没有返回值、参数,无法重载,在类的生命期结束的时候,被系统自动调用。
class A
{
public:
A(int a)
{
this->a = a;
cout << this->a << " 自定义默认的构造函数!" << endl;
}
~A()
{
//做清理扫尾工作
cout << this->a << "自定义的析构函数!" << endl;
}
private:
int a;
};
int main()
{
// 栈区
A a(1); //a创建的时候构造,销毁的时候析构
// 堆区
A *p = new A(2);//调用构造函数
delete p;//调用析构函数
return 0;
}
9、运算符重载
1)基本的运算符重载:
类型 operator 运算符 (形参表)
{
函数体;
}
class A
{
public:
A(int a)
{
this->a = a;
cout << "调用自定义构造函数!" << endl;
}
//运算符重载
A operator + (const A & tmp)
{
return A(this->a + tmp.a);
}
void print()
{
cout << a << endl;
}
private:
int a;
};
int main()
{
A a(111); // 调用有参构造函数
A b(222); // 调用有参构造函数
// +号运算符重载
A c = a + b;// 调用有参构造函数
c.print();
return 0;
}
2)赋值运算符重载函数(=):
也是一种特殊的成员函数。它用于对象之间的赋值。它本质上是重载类的“=”赋值操作符。
注:赋值运算符重载函数与靠背构造函数不同,后者是定义时触发。
class A
{
public:
A(int a)
{
this->a = a;
cout << "调用自定义构造函数!" << endl;
}
A(const A & tmp)
{
this->a = tmp.a;
cout << "调用自定义拷贝构造函数!" << endl;
}
void print()
{
cout << a << endl;
}
//自定义=赋值运算符重载函数
A & operator = (const A &tmp)
{
if (this == &tmp)
{
return *this; //直接返回自己
}
this->a = tmp.a; //进行赋值
cout << "自定义=赋值运算符重载函数" << endl;
return *this; //返回对象本身
}
private:
int a;
};
int main()
{
A a(111); //调用有参的构造函数
A b(a); //调用拷贝构造函数
A c = a; //调用拷贝构造函数
A e(222); //调用有参的构造函数
// 如果没有自定义的=运算符重载函数,则调用默认的
a = e;//没有调用拷贝构造,这个时候,对象没有发生构造,a早就构造好了
a.print();
a = a;//自己赋值给自己
a.print();
return 0;
}
赋值运算符重载函数与拷贝构造函数的区别:
- 拷贝构造函数时定义一个对象时被调用
- 赋值运算符重载函数是对一个已经存在的对象进行拷贝赋值(没有新的对象产生)