C++学习第六课:内存对齐、对象、this指针与构造函数

C++学习

C++学习第一课:C++学习须知
C++学习第二课:命名空间域
C++学习第三课:缺省函数与函数重载
C++学习第四课:引用与auto关键字
C++学习第五课:内联函数与初始类与对象
C++学习第六课:内存对齐、对象、this指针与构造函数



前言

本篇文章主要讲解了构造函数的相关内容,并简单复习了内存对齐的知识!

一、复习:内存对齐

类中既有成员变量又有成员函数

class A1{
public:
    void f1(){}
private:
    int _a;
    char _ch;
};

分析:A1的大小计算
答:_a存四个字节。_ch存一个字节紧跟其下,然后内存对齐补充三个字节,答案是4+1+3=8

class A1{
public:
    void f1(){}
private:
    char _ch;
    int _a;
};

分析:A1的大小计算
答:_ch存一个字节,_a存四个字节,所以刚刚的_ch内存对齐补充三个字节,也就是说_a的四个字节不会紧跟其下,而是先跟着3个字节,然后才是_a的四个字<<节,答案是1+3+4=8

为什么要内存对齐

(1)作用:提高效率
(2)如何实现作用:以上面两个例来说,如果我没对齐,一般一次就只读两个字节(这个读取长度由硬件决定),那要读3次,还有一些奇怪的拼接,那如何对齐,就一次读4个字节,虽然会读到不要的,但是我只需要读两次
(3)内存对齐的坏处:浪费空间(空间换时间)

类中只有成员函数

class A2{
public:
    void f2(){}
};
cout<<sizeof(A2)<<endl;

类中什么都没有

class A3
{};
cout<<sizeof(A3)<<endl;

分析:输出大小是多少?
答:输出大小为1。为了占位,表示对象存在。

二、对象

1.对象大小的计算

class A
{
public:
    void PrintA()
    {
        cout<<_a<<endl; 
    }
private:
    char _a;
};

注:计算大小时,c++和结构体一样要内存对齐,否则会有效率损失

三、this 指针

1.表现:一个类创建出来的不同对象执行类中的同一个函数时,输出结果可以不同

class balabla()
{
public:
    void print()
    {
        cout<<_year<<"-"<<_month<<"-"<<_day<<end1;
    }
}
balabala d1,d2;
d1.print();
d2.print();

2.this指针

    void print()
    {
        cout<<_year<<"-"<<_month<<"-"<<_day<<end1;
    }
    //编译器会处理为如下代码
   void print(Date* const this)
   {
       cout<<this->_year<<"-"<<this->_month<<"-"<<this->_day<<end1;
   }

3.注意事项

问题一:能不能自己写代码的时候就写this指针

(1)this是关键字,因此我们编写函数时不允许在实参和形参自己写上this
(2)函数内部可以显示使用this指针

问题二:能不能用如下方法进行调用

class balabala()
{
private:
    int _year;
}
int mian()
{
    Date::year;
}

答:不能,类里面的只是一个声明,找不到东西的

问题三: this指针存在哪里?

答:this指针是个形参,形参传给实参要进行压栈,因此this指针和普通参数一样存在与函数调用的栈帧里面

问题四:vs是如何对this进行优化的

答:this指针对象地址放在ecx,ecx存储this指针的值

问题五:分析下面代码

 //1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
    void Print()
    {
        cout << "Print()" << endl;
    }
private:
    int _a;
};
int main()
{
    A* p = nullptr;
    p->Print();
    return 0;
}

答:p调用Print,不会发生解引用,因为Print的地址不在对象中,print在公共代码段,p会作为实参传递给this指针,传递一个空指针不会报错,对空指针进行解引用才会报错,传递后this是空的,但是函数内没有对this指针解引用

// 2.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{ 
public:
    void PrintA() 
   {
        cout<<_a<<endl;
   }
private:
 int _a;
};
int main()
{
    A* p = nullptr;
    p->PrintA();
    return 0;
}

答:p调用Print,不会发生解引用,因为Print的地址不在对象中,print在公共代码段,p会作为实参传递给this指针,传递一个空指针不会报错,对空指针进行解引用才会报错,传递后this是空的,但是函数内访问_a,本质是this->a,需要进行解引用,导致报错

四、C语言与C++实现Stack的对比

C语言实现

typedef int DataType;
typedef struct Stack
{
    DataType* array;
    int capacity;
    int size;
}Stack;
void StackInit(Stack* ps)
{
    assert(ps);
    ps->array = (DataType*)malloc(sizeof(DataType) * 3);
    if (NULL == ps->array)
    {
        assert(0);
        return;
        ps->capacity = 3;
        ps->size = 0;
    }
void StackDestroy(Stack* ps)
{
    assert(ps);
    if (ps->array)
    {
        free(ps->array);
        ps->array = NULL;
        ps->capacity = 0;
        ps->size = 0;
    }
}
void CheckCapacity(Stack* ps)
{
    if (ps->size == ps->capacity)
    {
        int newcapacity = ps->capacity * 2;
        DataType* temp = (DataType*)realloc(ps->array, 
        newcapacity*sizeof(DataType));
    if (temp == NULL)
    {
        perror("realloc申请空间失败!!!");
        return;
    }
    ps->array = temp;
    ps->capacity = newcapacity;
 }
}
 }
 void StackPush(Stack* ps, DataType data)
{
    assert(ps);
    CheckCapacity(ps);
    ps->array[ps->size] = data;
    ps->size++;
}
int StackEmpty(Stack* ps)
{
    assert(ps);
    return 0 == ps->size;
}
void StackPop(Stack* ps)
{
    if (StackEmpty(ps))
    return;
    ps->size--;
}
DataType StackTop(Stack* ps)
{
    assert(!StackEmpty(ps));
    return ps->array[ps->size - 1];
 }
int StackSize(Stack* ps)
{
    assert(ps);
    return ps->size;
}
int main()
{
    Stack s;
    StackInit(&s);
    StackPush(&s, 1);
    StackPush(&s, 2);
    StackPush(&s, 3);
    StackPush(&s, 4);
    printf("%d\n", StackTop(&s));
    printf("%d\n", StackSize(&s));
    StackPop(&s);
    StackPop(&s);
    printf("%d\n", StackTop(&s));
    printf("%d\n", StackSize(&s));
    StackDestroy(&s);
    return 0;
}

C++实现

typedef int DataType;
class Stack
{
public:
    void Init()
    {
        _array = (DataType*)malloc(sizeof(DataType) * 3);
        if (NULL == _array)
    {
    perror("malloc申请空间失败!!!");
    return;
 }
_capacity = 3;
_size = 0;
}
void Push(DataType data)
{
    CheckCapacity();
    _array[_size] = data;
    _size++;
}
void Pop()
{
    if (Empty())
    return;
    _size--;
}
    DataType Top(){ return _array[_size - 1];}
    int Empty() { return 0 == _size;}
    int Size(){ return _size;}
void Destroy()
{
    if (_array)
    {
        free(_array);
        _array = NULL;
        _capacity = 0;
        _size = 0;
    }
}
private:
    void CheckCapacity()
    {
        if (_size == _capacity)
    {
    int newcapacity = _capacity * 2;
    DataType* temp = (DataType*)realloc(_array, newcapacity *
    sizeof(DataType));
    if (temp == NULL)
    {
        perror("realloc申请空间失败!!!");
        return;
    }
    _array = temp;
    _capacity = newcapacity;
    } 
    }
private:
    DataType* _array;
    int _capacity;
    int _size;
};
int main()
{
    Stack s;
    s.Init();
    s.Push(1);
    s.Push(2);
    s.Push(3);
    s.Push(4);
 
    printf("%d\n", s.Top());
    printf("%d\n", s.Size());
    s.Pop();
    s.Pop();
    printf("%d\n", s.Top());
    printf("%d\n", s.Size());
    s.Destroy();
    return 0;
}

对比:C++在调用函数时是“自动挡”,编译器会自行补很多操作

五、类的6个默认成员函数

(1)代码编写时经常遇到的问题:1.忘记初始化 2.忘记销毁直接返回
(2)解决上面两个问题的方法:6个默认成员函数
(3)作用/目的:自动进行初始化和销毁

1.初始化与清理

一、 构造函数主要完成初始化工作(代替init

(1)构造函数:构造函数是特殊的成员函数

特殊在哪里?
(1)函数名与类名相同
(2)无返回值(也不用写void
(3)对象实例化时编译器 自动调用 对应的构造函数
(4)构造函数可以重载
(5)如果类中没有显示定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显示定义编译器将不再生成***(此处可看六、祖师爷的失误)***

(2)构造函数怎么写?

class Date
 {
  public:
      // 1.无参构造函数
      Date()
     {}
  
      // 2.带参构造函数
      Date(int year, int month, int day)
     {
          _year = year;
          _month = month;
          _day = day;
     }
  private:
      int _year;
      int _month;
      int _day;
 };

构造函数能不能写成私有的

答:可以,但是暂时不用管,会导致无法调用

(3)构造函数怎么调用?

void TestDate()
 {
      Date d1; // 调用无参构造函数
      Date d2(2015, 1, 1); // 调用带参的构造函数
  
      // 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
      // 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
      // warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)
      Date d3();
 }

注:构造函数虽然叫做构造,但是主要任务并不是开空间创建对象,而是初始化对象

(4)什么时候自己写构造函数,什么时候用默认

结论先行:
(1)一般有内置类型成员就需要自己写,不能用编译器自己生成的
(2)全部都是自定义类型成员时可以考虑让编译器自己生成

(5)构造函数的调用

class Date
{
public:
    Date()
    {
        _year = 1;
        _month = 1;
        _day = 1;
    }
}
int main()
{
    Date d1();//不可以这样写,这样写和函数声明会冲突
    Date d2(2023,1,1);
}

二、析构函数主要完成函数工作(代替Destroy

(1)析构函数:特殊的成员函数

析构函数的特别之处
(1)析构函数名是在类名前加上字符~
(2)无参数无返回值值类型
(3)一个类只能有一个析构函数,若未显示定义,系统会自动生成默认的析构函数
(5)析构函数不能重载
(6)对象声明周期结束时,C++编译系统会自动调用析构函数

(2)如何书写析构函数

class Time
{
public:
    ~Time()
    {
        cout << "~Time()" << endl;
    }
private:
    int _hour;
    int _minute;
    int _second;
};

2.拷贝复制

拷贝构造是使用同类对象初始化创建对象

赋值重载主要是把一个对象赋值给另一个对象

3.取地址重载

主要是普通对象和const对象取地址,这两个很少会自己实现

六、祖师爷的失误

C++把类型分为两类

(1)内置类型/基本类型:int/char/double…
(2)自定义类型:用struct/class等定义的类型

失误之处:编译器默认生成构造函数,内置类型大多数编译器不做初始化处理,有的会处理,默认成不处理,定义类型会去调用他的默认构造,祖师爷规定一定处理

(3)举例:构造函数没有显示化时,系统会自动调用默认构造函数,但是这个构造函数未必具有初始化的功能

注意:C++11支持下面这种写法,这里不是初始化,只是声明,给的是默认的缺省值,给编译器生成默认的构造函数用

private:
    int _year = 1;
    int _month = 1;
    int _day = 1;

七、散装知识点

(1)默认构造函数:不传参就能调用的就是默认构造函数
(2)默认函数只能有一个

注意:无参构造函数、全缺省构造函数、我们没写而编译器默认生成的构造函数,都可以认为是默认构造函数

总结

本篇内容太散太杂,主要讲解了构造函数的相关内容,并简单复习了内存对齐!杂且多的知识需要反反复复的学习观看

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值