C++类和对象(2)

1、构造函数

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证 每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。构造函数的主要任务并不是开空间创建对象,而是初始化对象。

其特征如下:

1 函数名与类名相同。

2. 无返回值。

3. 对象实例化时编译器自动调用对应的构造函数。

4. 构造函数可以重载。

5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。

6. C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类 型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型.系统自动生成的构造函数对自定义类型,会调用该自定义类型的默认构造函数。内置类型成员变量在类中声明时可以给默认值。(这里给的值其实是给初始化列表提供参数)

7. 默认构造函数:无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。 注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。

推荐手写一个全缺省的构造函数,这样满足多个情况下对象的创建。在.h文件的类中直接写构造函数可以,.h文件和.cpp文件声明和定义分离也是可以的,而且更加推荐的是声明和定义分离。注意,在类中的函数放在.h文件中,被多个.cpp文件重复包含时不会产生重复定义的问题,因为没有出类的作用域。

2、析构函数

对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

析构函数的特征:

1. 析构函数名是在类名前加上字符 ~

2. 无参数无返回值类型

3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载

4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

5. 关于编译器自动生成的析构函数,处理的成员同样分为内置类型和自定义类型。内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;对自定类型成员则调用它本身的析构函数。

6. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如 Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。下面这个例子便是动态开辟了资源空间,那么在析构函数中就需要清理资源。

3、拷贝构造函数

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存 在的类类型对象创建新对象时由编译器自动调用。

拷贝构造函数的特性:

1. 拷贝构造函数是构造函数的一个重载形式。

2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。

为什么会引发无穷递归调用呢?

因为如果参数是类类型对象,那么就是传值调用,在传值调用时会生成一个类类型对象的临时拷贝,这份临时拷贝的生成原理其实便是拷贝构造函数,这样两者之间便形成了无穷的递归。所以参数类型要是类类型对象的引用(理论上来说指针也是可以的,但指针不符合用法习惯,在c++中指针也比较少用到)。

3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。

4.类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

例如:在Stack类中,有一个成员对象是指针,指向栈顶。在调用拷贝构造函数时,例如先Stack出一个A,再用A生成Stack B(A);时,B中的指针指向和A同一块地方,后面再调用析构函数时,便会发生重复释放同一块内存,便会报错。

5. 拷贝构造函数典型调用场景:

①使用已存在对象创建新对象,如Stack B(A);

②函数参数类型为类类型对象,这里便是传值调用;

③函数返回值类型为类类型对象。

注:为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用 尽量使用引用。

4、赋值运算符重载以及其他运算符重载

1、运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

写法简而言之,就是和正常函数一样,只不过函数名是 operator符号 ,下面是一些注意点:

①不能通过连接其他符号来创建新的操作符:比如operator@

②重载操作符必须有一个类类型参数

③用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义

④作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this

.*  ::  , sizeof ?:  ,  注意以上5个运算符不能重载

2、赋值运算符重载格式

①参数类型:const T&,传递引用可以提高传参效率;

②返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值

③检测是否自己给自己赋值

④返回*this :要符合连续赋值的含义

3、赋值运算符只能重载成类的成员函数不能重载成全局函数;如果一定需要赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数。这是因为,赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现 一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。

用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。和析构函数类似,如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。

其他的运算符重载同样需要注意这些问题,要注意根据原运算符的含义,从而还原对于当前类对象的操作,例如+,-,* ,/,等等操作符。

有个特殊的操作符,前置++和后置++。前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载,C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递;注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存 一份,然后给this+1;而temp是临时对象,因此只能以值的方式返回,不能返回引用。

Date& operator++()
 {
 _day += 1;
 return *this;
 }//日期类的前置++

Date operator++(int)
 {
 Date temp(*this);
 _day += 1;
 return temp;
 }//日期类的后置++

5、const总结

原文链接:超级详细的 C++ const 全面总结-CSDN博客

1、 const 修饰变量,以下两种定义形式在本质上是一样的。它的含义是:const 修饰的类型为 T 的变量 val,它是不可变的。

T const val = value;
const T val = value;

2、指针使用 const:

①如果 const 的位置在*的后面,表示指针本身是常量,但是指针指向的变量的值是可以改变的。简单来说,假如指针 p 指向了 变量 a,那么 p 就只能指向 a,不能再指向其他变量了,但是 a 不是常量,a 的值是可以改变的

int a = 10;
int b = 20;
int* const p = &a;

p = &b;  //error: assignment of read-only variable 'p'
*p = 30;  // 此时 a = 30

②如果 const 的位置在*的前面,表示指针指向是内容常量,但是指针本身是可变的。简单来说,假如指针 p 指向了 变量 a,那么 变量 a 就变成常量了,a 中的内容就不能被修改了,但是 p 本身可以改变,p 可以指向其他变量。

int a = 10;
int b = 20;
const int* p = &a;

*p = 30;  //error: assignment of read-only location '* p'
p = &b;  //correct: p 指向了b,*p = 20

3、函数使用const

①下面这种情况,传递过来的参数在函数内部不可改变(无意义,因为 var 本身就是形参)

void function(const int var);

②参数指针所指内容为常量不可变

void function(const int* p);

③参数指针本身是常量不可变

void function(int* const p);

④const修饰引用,引用内容不可改变

void function(const class& A); //引用参数在函数内不可改变
void function(const int &a); //引用参数在函数内为常量不可变

⑤const修饰函数返回值(应用的比较少)

1. const int func1();  //这个其实没多大意义,因为参数返回就是赋值
2. const int* func2(); //调用时:const int* p = func2(); 可以把 func2() 当作一个变量,即指针内容不可变
3. int* const func3(); //调用时:int* const p = func3(); 以把 func2() 当作一个变量,即指针本身不可变

一般情况下,函数的返回值为某个对象时,如果将其声明为 const 时,多用于操作符的重载。通常,不建议用 const 修饰函数的返回值类型为某个对象或者某个对象的引用的情况。
原因如下:如果返回值为某个对象为 const(比如:const A test = A; )或某个对象的引用为 const(const A& test = A;),则返回值具有 const 属性,则返回实例只能访问类 A 中的公有或保护数据成员和 const 成员函数,并且不允许对其进行赋值操作,这在一般情况下很少用到。

4、类使用const

 const 修饰成员变量
 const 修饰类的成员变量,表示成员常量,不能被修改,同时它也只能在初始化列表中赋值

class A{
	const int a;  //成员常量不能被修改
	……
	A(int _a) : a(_a){}  //只能在初始化列表中赋值 
};

const 修饰成员函数

const 修饰类的成员函数,则该成员函数不能调用类中任何非 const 成员函数,一般写在函数的最后来修饰

class A{
	void func() const{} //常成员函数,它不能改变对象的成员变量,也不能调用类中任何非 const 成员函数
};

对于 const 类对象/指针/引用,只能调用类的 const 成员函数。因此,const 修饰成员函数的最重要的作用就是限制对于 const 对象的使用。

const 成员函数不被允许修改它所在对象的任何一个数据成员
对于第 1 点,const 成员函数不被允许修改它所在对象的任何一个数据成员。这里有一个注意点,如果类中存在指针类型的数据即便是 const 函数也只能保证不修改该指针的值,并不能保证不能修改该指针指向的内容。

class Name {
public:
	void setName(const string &s) const;
private:
    char *m_sName;
};

void Name::setName(const string &s) const {
//    m_sName = s.c_str();      // 错误!不能修改m_sName;

for (int i = 0; i < s.size(); ++i)
    m_sName[i] = s[i];    // 不好的风格,但不是错误的
}

const修饰成员函数的原理:将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数 隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

const 成员函数能够访问对象的 const 成员,而其他函数不可以

这个结论不严谨。const function 的本质,其实就是会在函数内部,把当前调用者的 this 指针转换成 const this,以保证当前函数对数据成员只有读的权限,没有写的权限。如果在非 const 成员函数里只访问了 const 成员变量,但没有进行修改操作,这时候是可以通过编译的。

class A
{
 private:
     const int a;
 public:
    A(int _a) : a(_a){}
    int getA(){ cout << a; } // ok,可以通过
    int setA(){ a = 30; } //error
};

const 修饰类对象/对象指针/对象引用

  • const 修饰类对象表示该对象为常量对象,其中的任何成员都不能被修改,对于对象指针和对象引用也是一样
  • const 修饰的对象,该对象的任何非 const 成员函数都不能被调用,因为任何非 const 成员函数会有修改成员变量的企图

结论:

const对象可以访问const成员函数,但是不能访问非const成员函数
非const对象可以调用const 成员函数和非const成员函数
const成员函数可以调用其他const成员函数,不能调用其他非const成员函数
非const成员函数内可以调用其他const成员函数和非const成员函数

6、取地址及const取地址操作符重载

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需 要重载,比如想让别人获取到指定的内容。

class Date
{ 
public :
 Date* operator&()
 {
 return this ;
}
 
 const Date* operator&()const
 {
 return this ;
 }
private :
 int _year ; // 年
 int _month ; // 月
 int _day ; // 日
};

  • 17
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值