C++面向对象特性之封装

一、类的成员变量

普通成员变量

数据成员初始化的方式有三种:1.默认初始化,类内成员没有被赋初值时执行默认初始化,类类型成员怎么初始化由其本身类型决定;2.类内初始化,即在构造函数之前给其赋初始值,可以用列表初始化或一般初始化;3.构造函数成员列表初始化,当数据成员没有在成员列表中出现时,将执行上面两种初始化方式,成员列表初始化顺序和声明顺序一致,与在列表中出现顺序无关。

在构造函数体内对变量赋值不属于初始化,只是初始化后的赋值阶段。像const和引用,如果没有初始化就在构造函数中赋值则会报错。

可变成员变量:

一般在const成员函数中不能修改成员变量,但只要成员变量被声明成mutable可变的则可以改变它。用处在于有时候需要在const函数界面修改与类状态无关的成员变量,可以用mutable,mutable不能修饰静态成员变量。

静态成员变量

一是不管定义多少个类对象,静态数据成员都共享分配在全局数据区的内存,所以节省了存储空间。

二是一旦某变量需要改变时,只要改变一次,则所有类对象的该变量全改变过来了。

三是有一些状态是和类本身相关的而不是和对象相关的 这些状态数据可以用静态成员变量去表达

class Rectangle
{
  private:
    static int count;
    int high,width;
  public:
    //要么不写 要么Rectangle()=default这个和下一个两者只能留一个
    Rectangle(){count++;}
    Rectangle(int high_val,int width_val=5);//定义默认值
    Rectangle(const Rectangle& ant_rec);
    //要么不写 要么~Rectangle()=default这个和下一个两者只能留一个
    Rectangle(){count--;}
    int get_area();
    static int get_count()
    {
        return count;
    }
};
int Rectangle::count=0;

C++类的静态成员变量需要在类外初始化
静态成员变量在类中仅仅是声明,没有定义,编译器暂时不会给它分配内存
在类的外面定义,实际上是给静态成员变量分配内存。
-静态成员变量在程序内部位于全局数据区(Type className::VarName = value)
-静态成员变量需要在类外单独分配空间
如果没有在类外定义的话,编译的时候会出现对这个变量未定义的引用

cout<<"rec count: "<< Rectangle::count<<endl;  //可以通过类名直接访问公有静态成员变量

特性

静态成员与普通成员的区别
1)生命周期
静态成员变量从类被加载开始到类被卸载,一直存在;
普通成员变量只有在类创建对象后才开始存在,对象结束,它的生命期结束;
2)共享方式
静态成员变量是全类共享;普通成员变量是每个对象单独享用的;
3)定义位置
普通成员变量存储在栈或堆中,而静态成员变量存储在静态全局区;
4)初始化位置
普通成员变量在类中初始化;静态成员变量在类外初始化;
5)默认实参
可以使用静态成员变量作为默认实参

静态成员变量和全局变量的区别:

同全局变量相比,使用静态数据成员有两个优势:

(1)静态数据成员没有进入程序的全局命名空间,因此不存在与程序中其他全局名字冲突的可能性;

(2)可以实现信息隐藏。静态数据成员可以使private成员,而全局变量不能。

二、类的成员函数

构造函数

每个类都分别定义了它的对象被初始化的方式,类通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数叫做构造函数( constructor)。构造函数的任务是初始化类对象的数据成员,无论何时只要类的对象被创建,就会执行构造函数。构造函数不分配内存。

当未定义构造函数或在默认构造函数后加=default编译器会生成默认构造函数。在默认构造函数后面加delete,就算生成了默认构造也不能使用。什么时候要自定义构造函数?
基类的构造函数有一组参数。
数据成员对象的构造函数有一组参数。
数据成员中有const成员,reference成员,或者希望给普通数据成员自定义赋值。

特性
    1.构造函数可以被重载
    2.构造函数不能以本类对象作为唯一参数 避免和拷贝构造函数混淆

静态成员的复制问题 : 默认拷贝构造函数不会处理静态数据成员。成员变量复制的时候不会复制静态成员变量。如果一个类,加入了一个静态成员进行计数。在主函数中,首先创建对象rect1,输出此时的对象个数,然后使用rect1复制出对象rect2,再输出此时的对象个数,此时应该有两个对象存在,但实际程序运行时,输出的都是1。此外,在销毁对象时,由于会调用销毁两个对象,类的析构函数会调用两次,此时的计数器将变为负数。

    解决方式是:自己定义拷贝构造函数 ,然后在这个自定义的拷贝构造函数里面加1。

指针的复制问题(浅拷贝)

浅拷贝指的是在对象复制时,只对对象中的数据成员进行简单的赋值,默认拷贝构造函数执行的也是浅拷贝。大多情况下“浅拷贝”已经能很好地工作了,但是一旦对象存在了动态成员,那么浅拷贝就会出问题了。


  在销毁对象时,两个对象的析构函数会对同一个内存空间释放两次,所以会出错。我们需要的是两个指针指向的空间有相同的值。 解决方式:

         方法一:将拷贝函数声明为私有 ,防止默认拷贝构造函数执行 ,这样进行拷贝的时候就会报错
        方法二:在自定义的拷贝构造函数里面执行深拷贝,有自定义拷贝构造就不会有默认拷贝构造

注意,如果一个类中只存在一 个参数为 X&的拷贝构造函數那么就不能使用const X或volatile X的对象实行拷贝初始化。拷贝构造是函数生成新对象,拷贝运算符是重载不生成新对象。

为什么拷贝构造函数必须是引用传递,不能是值传递?
为了防止递归引用.。当一个对象需要以值方式传递时,编译器会生成代码调用它的拷贝构造函数以生成一个复本。如果类A的拷贝构造函数是以值方式传递一个类A对象作为参数的话,当需要调用类A的拷贝构造函数时,需要以值方式传进一个A的对象作为实参; 而以值方式传递需要调用类A的拷贝构造函数;结果就是调用类A的拷贝构造函数导致又一次调用类A的拷贝构造函数,这就是一个无限递归。

析构函数

释放对象使用的资源并销毁对象的非static成员,不能重载。析构函数有一个函数体和析构部分,首先执行函数体,然后按初始化顺序的逆序调用析构。同样可以在析构函数后面届=default强制生成默认析构函数。如果自己定义了析构函数,则需要在类的结尾自己显式的去调用析构函数 否则会报错,因为编译器不再生成默认的析构函数。如果自己定义了析构函数 但是只声明没有定义  则就算在类的最后显式调用了析构函数也会报错。

三、类的this指针

1.当我们调用成员函数时,实际上是替某个对象调用它,用请求该函数的对象地址初始化 this。

2.成员函数默认会隐式的包含this指针形参

3.在成员函数中所有对成员变量的调用都会默认转换成用this指针对成员变量的调用

4.this指针只能在成员函数中使用,在全局函数、静态成员函数中都不能用this。

5.在成员函数中调用delete this会出现什么问题?
当调用delete this时,类对象的内存空间被释放。在delete this之后进行的其他任何函数调用,
只要不涉及到this指针的内容,都能够正常运行。一旦涉及到this指针,
如操作数据成员,调用虚函数等,就会出现不可预期的问题。

四、总结

构造函数在所有函数调用之前执行,在主函数执行完成后调用析构函数。

C++的空类有哪些成员函数:
默认构造函数。
默认拷贝构造函数。
默认析构函数。
默认赋值运算符。
默认取址运算符。
默认取址运算符const。
后面这两个函数也是空类的默认函数(Ref:《effectivec++》 )。只有当实际使用这些函数的时候,编译器才会定义它们。

一个C++类对象的存储空间?

//空类
class classA
{
};
答案是1B。在声明该类型实例的时候,必须给实例在内存中分配一定的空间,否则无法使用该实例。由于空类型不含任何信息,故而所占的内存大小由编译器决定。codeblocks和Visual Studio中每个空类型的实例占1B。在该类中添加成员函数(比如构造函数和析构函数),
class classA
{
    classA(){}
    ~classA(){}
};
在该类中添加构造函数和析构函数,再对该类型求sizeof,结果仍为1B。因为成员函数只与类型相关,而与具体实例无关(类确定时函数地址就定下来了,调用时只要根据函数地址找到函数即可)。如果有其他成员函数(非虚函数),则还是只占用1个字节。在类中添加成员变量,
class classA{
private :
    int a 
public:
    classA(){}
    ~classA(){}
}

结果
1.为类的非静态成员数据的类型大小之和.
3.为了优化存取效率,进行的边缘调整(对齐)。在类中添加虚函数,
class classA{
private :
    int a ;
public:
    classA(){};
    ~classA(){};
    virtual  temp(){};
}

C++编译器一旦发现类型中有虚函数,就会为该类型生成虚函数表,并在该类型的每个实例中添加一个指向虚函数表的指针。在32位的机器上,一个指针占4B;在64位的机器上,一个指针占8B。
总结:
类的大小
1.为类的非静态成员数据的类型大小之和.
2.由编译器额外加入的成员变量的大小,用来支持语言的某些特性(如:指向虚函数的指针).
3.为了优化存取效率,进行的边缘调整(对齐).
4.与类中的构造函数,析构函数以及其他的成员函数无关.
 5.当该该类是某类的派生类,那么派生类继承的基类部分的数据成员也会存在在派生类中的空间中,也会对派生类进行扩展。
空类的大小不是0 取决于不同的编译器 vscode是1字节

 
设计一个类计算子类的个数
1.为类设计一个static静态变量count作为计数器;
2.类定义结束后初始化count;
3.在构造函数中对count进行+1;
4.设计拷贝构造函数,在进行拷贝构造函数中进行count +1,操作;
5.设计复制构造函数,在进行复制函数中对count+1操作;
6.在析构函数中对count进行-1;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值