C++深度解析教程笔记8

C++深度解析教程笔记8

  • 第17课 - 对象的构造(上)
    • 类定义中成员变量i和j的初始值?
    • 实验-成员变量的初始值
    • 对象初始化解决方案1
    • 实验-手动调用函数初始化对象
    • 对象初始化解决方案2:构造函数
    • 实验-构造函数
    • 小结
  • 第18课 - 对象的构造(中)
    • 带参数的构造函数
    • 对象定义和对象声明的区别
    • 实验-带参数的构造函数
    • 构造函数的调用
    • 实验-创建对象数组的步骤
    • 实验-数组类
    • 小结
  • 第19课 - 对象的构造(下)
    • 实验-默认的拷贝构造函数
    • 实验-深拷贝与浅拷贝
    • 实验-数组类的改进:增加拷贝构造函数
    • 小结
  • 第20课 - 初始化列表的使用
    • 实验
    • 实验-初始化列表
    • 实验-初始化const变量并修改值
    • 小结
  • 第21课 - 对象的构造顺序
    • 实验
    • 实验
    • 实验
    • linux结果
    • windows结果
    • 小结
  • 第22课 - 对象的销毁
    • 实验-销毁对象自动调用析构函数
    • 小结
  • 第23课 - 神秘的临时对象
    • 实验-临时对象
      • 代码优化
    • 实验-编译器对临时对象的优化
    • 小结
  • 第24课 - 经典问题解析二
    • 实验-多对象析构函数顺序
    • 实验-const成员函数
    • 实验-this的使用

本文学习自狄泰软件学院 唐佐林老师的 C++深度解析教程,图片全部来源于课程PPT,仅用于个人学习记录

第17课 - 对象的构造(上)

类定义中成员变量i和j的初始值?

class Test
{
private:int i;int j;
public:int getI() { return i; }int getJ() { return j; }
};

实验-成员变量的初始值

#include <stdio.h>class Test
{
private:int i;int j;
public:int getI() { return i; }int getJ() { return j; }
};Test gt;int main()
{printf("gt.i = %d\n", gt.getI());//0printf("gt.j = %d\n", gt.getJ());//0Test t1;printf("t1.i = %d\n", t1.getI());//随机printf("t1.j = %d\n", t1.getJ());//随机Test* pt = new Test;printf("pt->i = %d\n", pt->getI());//随机printf("pt->j = %d\n", pt->getJ());//随机delete pt;return 0;
}
/*
E:\test>g++ 17-1.cppE:\test>a
gt.i = 0
gt.j = 0
t1.i = 14380624
t1.j = 0
pt->i = 39267488
pt->j = 0E:\test>a
gt.i = 0
gt.j = 0
t1.i = 1928784
t1.j = 0
pt->i = 6499488
pt->j = 0E:\test>a
gt.i = 0
gt.j = 0
t1.i = 13921872
t1.j = 0
pt->i = 38808736
pt->j = 0*/

对象的本质上就是变量
在栈和堆上创建对象时,成员变量的初始值是随机的
在静态存储区创建对象时,成员变量初始值为0

对象初始化解决方案1

在类中提供public的initialize函数,对象创建后手动调用此函数进行初始化

#include <stdio.h>class Test
{
private:int i;int j;
public:int getI() { return i; }int getJ() { return j; }void initialize(){i = 0;j = 0;}
};

实验-手动调用函数初始化对象

#include <stdio.h>class Test
{
private:int i;int j;
public:int getI() { return i; }int getJ() { return j; }void initialize(){i = 1;j = 2;}
};Test gt;int main()
{gt.initialize();printf("gt.i = %d\n", gt.getI());//1printf("gt.j = %d\n", gt.getJ());//2Test t1;//t1.initialize();printf("t1.i = %d\n", t1.getI());//随机值printf("t1.j = %d\n", t1.getJ());//随机值t1.initialize();Test* pt = new Test;pt->initialize();printf("pt->i = %d\n", pt->getI());//1printf("pt->j = %d\n", pt->getJ());//2delete pt;return 0;
}

对象初始化解决方案2:构造函数

initialize初始化的问题:普通函数,必须显示调用;若未调用,运行结果不确定
C++中与类名相同的成员函数叫构造函数
特点:没有任何返回类型的声明;构造函数在对象定义时自动被调用

实验-构造函数

#include <stdio.h>class Test
{
private:int i;int j;
public:int getI() { return i; }int getJ() { return j; }Test()//构造函数{printf("Test() Begin\n");i = 1;j = 2;printf("Test() End\n");}
};Test gt;int main()
{printf("gt.i = %d\n", gt.getI());printf("gt.j = %d\n", gt.getJ());Test t1;printf("t1.i = %d\n", t1.getI());printf("t1.j = %d\n", t1.getJ());Test* pt = new Test;printf("pt->i = %d\n", pt->getI());printf("pt->j = %d\n", pt->getJ());delete pt;return 0;
}
/*
Test() Begin
Test() End
gt.i = 1
gt.j = 2
Test() Begin
Test() End
t1.i = 1
t1.j = 2
Test() Begin
Test() End
pt->i = 1
pt->j = 2
*/

小结

每个对象在使用之前都应该初始化
类的构造函数用于对象的初始化
构造函数与类同名且无返回值
构造函数在对象定义时自动被调用

第18课 - 对象的构造(中)

带参数的构造函数

构造函数可以根据需要定义参数
一个类中可以存在多个重载的构造函数
构造函数的重载遵循C++重载的规则

class Test
{
public:Test() { printf("Test()\n");}Test(int v) //带参数的构造函数{ printf("Test(int v), v = %d\n", v);}
};

对象定义和对象声明的区别

对象定义:申请对象的空间并调用构造函数
对象声明:告诉编译器存在这样一个对象

实验-带参数的构造函数

#include <stdio.h>class Test
{
public:Test() { printf("Test()\n");}Test(int v) //带参数的构造函数{ printf("Test(int v), v = %d\n", v);}
};int main()
{Test t;      // 调用 Test()Test t1(1);  // 调用 Test(int v)Test t2 = 2; // 调用 Test(int v)int i(100);//<------->int i=100;printf("i = %d\n", i);return 0;
}
/*
E:\test>a
Test()
Test(int v), v = 1
Test(int v), v = 2
i = 100
*/

构造函数的调用

一般情况下,构造函数在对象定义时被自动调用
特殊情况下,需要手工调用构造函数

实验-创建对象数组的步骤

#include <stdio.h>class Test
{
private:int m_value;
public:Test() { printf("Test()\n");m_value = 0;}Test(int v) { printf("Test(int v), v = %d\n", v);m_value = v;}int getValue(){return m_value;}
};int main()
{Test ta[3] = {Test(), Test(1), Test(2)};      for(int i=0; i<3; i++){printf("ta[%d].getValue() = %d\n", i , ta[i].getValue());}Test t = Test(100);printf("t.getValue() = %d\n", t.getValue());return 0;
}
/*
Test()
Test(int v), v = 1
Test(int v), v = 2
ta[0].getValue() = 0
ta[1].getValue() = 1
ta[2].getValue() = 2
Test(int v), v = 100
t.getValue() = 100*/

示例
开发一个数组类解决原生数组的安全性问题
提供函数 获取数组长度、获取数组元素、设置数组元素
类的私有成员:长度、头指针
类的公有成员:构造函数、返回长度的函数、获取和设置元素的函数、free()函数

实验-数组类

//头文件
#ifndef _INTARRAY_H
#define _INTARRAY_H
class IntArray
{
private:int m_length;int * m_pointer;
public:IntArray(int len);int length();bool getvalue(int index,int& value);bool setvalue(int index,int value);void free();};//;
#endif//.cpp
#include "IntArray.h"
IntArray::IntArray(int len)
{m_length=len;m_pointer=new int[len];//(int*)malloc(sizeof(int)*len);for(int i=0;i<len;i++){m_pointer[i]=0;}}
int IntArray::length()
{return m_length;}bool IntArray::getvalue(int index,int& value)//int & value
{bool res=(index>=0)&&(index<m_length);if(res){value=m_pointer[index];}return res;
}
bool IntArray::setvalue(int index,int value)
{bool res=(index>=0)&&(index<m_length);if(res){m_pointer[index]=value;}return res;}
void IntArray::free(){delete[] m_pointer;}//main
#include <stdio.h>
#include "IntArray.h"int main()
{IntArray a(5);a.setvalue(2,2);
//    for(int i=0;i<a.length();i++)
//    {
//
//        printf("a[%d]=%d\n",i,a[i]);
//    }
//    for(int i=0; i<a.length(); i++)
//    {
//        a.setvalue(i,i + 3);
//    }for(int i=0; i<a.length(); i++){int value = 0;if( a.getvalue(i, value) ){printf("a[%d] = %d\n", i, value);}}return 0;
}/*
a[0] = 0
a[1] = 0
a[2] = 2
a[3] = 0
a[4] = 0
*/

小结

构造函数可以根据需要定义参数
构造函数之间可以存在重载关系
构造函数遵循C++中重载函数的规则
对象定义时会触发构造函数的调用
在一些情况下可以手动调用构造函数

第19课 - 对象的构造(下)

两种特殊的构造函数
无参构造函数 没有参数的构造函数
当类中未定义构造函数时,编译器默认提供一个无参构造函数,并且其函数体为空
拷贝构造函数 参数为const class_name&
当类中没有定义拷贝构造函数时,编译器默认提供一个拷贝构造函数,进行变量值的赋值

实验-默认的拷贝构造函数

#include <stdio.h>class Test
{
private:int i;int j;
public:int getI(){return i;}int getJ(){return j;}Test(const Test& t){i = t.i;j = t.j;}Test(){}/**/
};int main()
{Test t1;Test t2 = t1;printf("t1.i = %d, t1.j = %d\n", t1.getI(), t1.getJ());printf("t2.i = %d, t2.j = %d\n", t2.getI(), t2.getJ());return 0;
}
/*
无拷贝构造函数、构造函数代码 结果
t1.i = 2, t1.j = 0
t2.i = 2, t2.j = 0
有拷贝构造函数、构造函数代码 结果
t1.i = 2, t1.j = 0
t2.i = 2, t2.j = 0
*/

浅拷贝深拷贝区别
拷贝后的对象 物理状态相同 逻辑状态相同
编译器提供的拷贝构造函数只进行浅拷贝

拷贝构造函数的意义 兼容c语言的初始化方式
初始化行为符合预期的逻辑

实验-深拷贝与浅拷贝

#include <stdio.h>class Test
{
private:int i;int j;int* p;
public:int getI(){return i;}int getJ(){return j;}int* getP(){return p;}//case1/*Test(const Test& t){i = t.i;j = t.j;p = new int;*p = *t.p;}*/Test(int v){i = 1;j = 2;p = new int;*p = v;}void free(){delete p;}
};int main()
{//Test t1;Test t1(3);Test t2=t1;//printf("t1.i = %d, t1.j = %d, *t1.p = %d\n", t1.getI(), t1.getJ(), *t1.getP());//printf("t2.i = %d, t2.j = %d, *t2.p = %d\n", t2.getI(), t2.getJ(), *t2.getP());printf("t1.i = %d, t1.j = %d, t1.p = %p\n", t1.getI(), t1.getJ(), t1.getP());printf("t2.i = %d, t2.j = %d, t2.p = %p\n", t2.getI(), t2.getJ(), t2.getP());t1.free();t2.free();return 0;
}//case1下侧注释掉   浅拷贝
cyz@cyz-virtual-machine:~/桌面$ g++ test.cpp
cyz@cyz-virtual-machine:~/桌面$ ./a.out
t1.i = 1, t1.j = 2, t1.p = 0x61c63c0bbeb0
t2.i = 1, t2.j = 2, t2.p = 0x61c63c0bbeb0
free(): double free detected in tcache 2
已中止 (核心已转储)                          //报错,free2次p
//case1下侧不注释掉   深拷贝
cyz@cyz-virtual-machine:~/桌面$ g++ test.cpp
cyz@cyz-virtual-machine:~/桌面$ ./a.out
t1.i = 1, t1.j = 2, *t1.p = 3
t2.i = 1, t2.j = 2, *t2.p = 3
t1.i = 1, t1.j = 2, t1.p = 0x5aa51679ceb0    //b0
t2.i = 1, t2.j = 2, t2.p = 0x5aa51679ced0    //d0手动定义拷贝构造函数后,各个对象的指针p的地址不一样了

注意:自定义拷贝构造函数,必然需要实现深拷贝

3
t1.m_pointer
t2.m_pointer

t1.free();// 0x61c63c0bbeb0
t2.free();// 0x61c63c0bbeb0
对象中有成员指代了系统中的资源,需要深拷贝

  • 成员指向了动态内存空间
  • 成员打开了外存中的文件
  • 成员使用了系统的网络端口

实验-数组类的改进:增加拷贝构造函数

//IntArray.h
#ifndef _INTARRAY_H
#define _INTARRAY_H
class IntArray
{
private:int m_length;int * m_pointer;
public:IntArray(int len);//IntArray(const IntArray& obj);//深拷贝int length();int* getpointer();bool getvalue(int index,int& value);bool setvalue(int index,int value);void free();};//;
#endif//IntArray.cpp
#include "IntArray.h"
IntArray::IntArray(int len)
{m_length=len;m_pointer=new int[len];//(int*)malloc(sizeof(int)*len);for(int i=0;i<len;i++){m_pointer[i]=0;}}
/*拷贝构造函数
IntArray::IntArray(const IntArray& obj)
{m_length=obj.m_length;m_pointer=new int[m_length];//(int*)malloc(sizeof(int)*len);for(int i=0;i<m_length;i++){m_pointer[i]=obj.m_pointer[i];}}*/
int IntArray::length()
{return m_length;}
int* IntArray::getpointer(){return m_pointer;}bool IntArray::getvalue(int index,int& value)//int & value
{bool res=(index>=0)&&(index<m_length);if(res){value=m_pointer[index];}return res;
}
bool IntArray::setvalue(int index,int value)
{bool res=(index>=0)&&(index<m_length);if(res){m_pointer[index]=value;}return res;}
void IntArray::free(){delete[] m_pointer;}//main.cpp
#include "IntArray.h"int main()
{IntArray a(5);IntArray b=a;a.setvalue(2,2);printf("apointer = %p\n", a.getpointer());for(int i=0; i<a.length(); i++){int value = 0;if( a.getvalue(i, value) ){printf("a[%d] = %d\n", i, value);}}a.free();printf("bpointer = %p\n", b.getpointer());for(int i=0; i<b.length(); i++){int value = 0;if( b.getvalue(i, value) ){printf("b[%d] = %d\n", i, value);}}b.free();return 0;
}/*
默认拷贝构造函数
apointer = 001F1650
a[0] = 0
a[1] = 0
a[2] = 2
a[3] = 0
a[4] = 0
bpointer = 001F1650
b[0] = 2031808
b[1] = 2047592
b[2] = 2
b[3] = 0
b[4] = 0
//增加拷贝构造函数之后
apointer = 00761650
a[0] = 0
a[1] = 0
a[2] = 2
a[3] = 0
a[4] = 0
bpointer = 00761670
b[0] = 0
b[1] = 0
b[2] = 0
b[3] = 0
b[4] = 0*/

linux下运行对比

//浅拷贝
cyz@cyz-virtual-machine:~/桌面$ g++ main.cpp IntArray.cpp
cyz@cyz-virtual-machine:~/桌面$ ./a.out
apointer = 0x5c9e21c70eb0
a[0] = 0
a[1] = 0
a[2] = 2
a[3] = 0
a[4] = 0
bpointer = 0x5c9e21c70eb0
b[0] = -907928464
b[1] = 5
b[2] = -348087256
b[3] = 1712401683
b[4] = 0
free(): double free detected in tcache 2
已中止 (核心已转储)
//深拷贝apointer = 0x5844f6a32eb0
a[0] = 0
a[1] = 0
a[2] = 2
a[3] = 0
a[4] = 0
bpointer = 0x5844f6a32ed0
b[0] = 0
b[1] = 0
b[2] = 0
b[3] = 0
b[4] = 0

小结

C++编译器会默认提供构造函数
无参构造函数用于定义对象的默认初始状态
拷贝构造函数在创建对象时拷贝对象的状态
对象的拷贝方式区别:浅拷贝 深拷贝(对象使用了系统资源,手动编写拷贝构造函数)

第20课 - 初始化列表的使用

类中定义const成员

实验

#include <stdio.h>class Test
{
private:const int ci;
public:Test(){ci = 10;}int getCI() { return ci; }
};int main()
{Test t;printf("t.ci = %d\n", t.getCI());return 0;
}
E:\test>g++ 20-1.cpp
20-1.cpp: In constructor 'Test::Test()':
20-1.cpp:8:5: error: uninitialized const member in 'const int' [-fpermissive]Test()^~~~
20-1.cpp:6:15: note: 'const int Test::ci' should be initializedconst int ci;^~
20-1.cpp:10:14: error: assignment of read-only member 'Test::ci'ci = 10;

在这里插入图片描述
在这里插入图片描述
类成员的初始化
C++中提供了初始化列表对成员变量进行初始化
语法:

ClassName::ClassName:
m1(v1),m2(v1,v2),m3(v3)
{//初始化
}

实验-初始化列表

#include <stdio.h>class Value
{
private:int mi;
public:Value(int i){printf("i = %d\n", i);mi = i;}int getI(){return mi;}
};class Test
{
private:Value m2;Value m3;Value m1;
public:Test() : m1(1), m2(2), m3(3){printf("Test::Test()\n");}
};int main()
{Test t;return 0;
}
/*
i = 2
i = 3
i = 1
Test::Test()
顺序跟成员声明顺序一致
*/

在这里插入图片描述

实验-初始化const变量并修改值

#include <stdio.h>class Value
{
private:int mi;
public:Value(int i){printf("i = %d\n", i);mi = i;}int getI(){return mi;}
};class Test
{
private:const int ci;Value m2;Value m3;Value m1;
public:Test() : m1(1), m2(2), m3(3), ci(100){printf("Test::Test()\n");}int getCI(){return ci;}void setCI(int v){int* p = const_cast<int*>(&ci);*p = v;}
};int main()
{Test t;printf("t.ci = %d\n", t.getCI());t.setCI(10);printf("t.ci = %d\n", t.getCI());return 0;
}
/*
i = 2
i = 3
i = 1
Test::Test()
t.ci = 100
t.ci = 10*/

初始化:对正在创建的对象设置初始值
赋值:对已创建的对象设置值

小结

在这里插入图片描述

第21课 - 对象的构造顺序

定义多个对象,对象的构造顺序
在这里插入图片描述
在这里插入图片描述

实验

#include <stdio.h>class Test
{
private:int mi;
public:Test(int i){mi = i;printf("Test(int i): %d\n", mi);}Test(const Test& obj){mi = obj.mi;printf("Test(const Test& obj): %d\n", mi);}
};int main()
{int i = 0;Test a1 = i;while( i < 3 ){Test a2 = ++i;}if( i < 4 ){Test a = a1;}else{Test a(100);}return 0;
}
/*
Test(int i): 0
Test(int i): 1
Test(int i): 2
Test(int i): 3
Test(const Test& obj): 0*/

在这里插入图片描述
在这里插入图片描述

实验

#include <stdio.h>class Test
{
private:int mi;
public:Test(int i){mi = i;printf("Test(int i): %d\n", mi);}Test(const Test& obj){mi = obj.mi;printf("Test(const Test& obj): %d\n", mi);}int getMi(){return mi;}
};int main()
{int i = 0;Test* a1 = new Test(i); // Test(int i): 0while( ++i < 10 )if( i % 2 )new Test(i); // Test(int i): 1, 3, 5, 7, 9if( i < 4 )new Test(*a1);elsenew Test(100); // Test(int i): 100return 0;
}
/*Test(int i): 0
Test(int i): 1
Test(int i): 3
Test(int i): 5
Test(int i): 7
Test(int i): 9
Test(int i): 100*/

在这里插入图片描述

实验

//test.h
#ifndef _TEST_H_
#define _TEST_H_#include <stdio.h>class Test
{
public:Test(const char* s){printf("%s\n", s);}
};#endif//t1.cpp
#include "test.h"
Test t1("t1");//t2.cpp
#include "test.h"
Test t2("t2");//t3.cpp
#include "test.h"
Test t3("t3");//21-3.cpp
#include "test.h"
Test t4("t4");
int main()
{Test t5("t5");
}

linux结果

cyz@cyz-virtual-machine:~/桌面$ g++ 21-3.cpp t2.cpp t1.cpp t3.cpp -o test.out
cyz@cyz-virtual-machine:~/桌面$ ./test.out
t4
t2
t1
t3
t5

windows结果

E:\test>g++ 21-3.cpp t2.cpp t1.cpp t3.cpp -o test.out
E:\test>test.out
t3
t1
t2
t4
t5

全局对象的构造顺序是不定的

小结

局部对象的构造顺序依赖于程序执行流
堆对象的构造顺序依赖于new的使用顺序
全局对象的构造顺序是不确定的

第22课 - 对象的销毁

清理要销毁的对象

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

实验-销毁对象自动调用析构函数

#include <stdio.h>class Test
{int mi;
public:Test(int i){mi = i;printf("Test(): %d\n", mi);}~Test(){printf("~Test(): %d\n", mi);}
};int main()
{Test t(1);Test* pt = new Test(2);delete pt;return 0;
}
/*
Test(): 1
Test(): 2
~Test(): 2
~Test(): 1*/

在这里插入图片描述

小结

析构函数是对象销毁时进行清理的特殊函数
析构函数在对象销毁时自动被调用
析构函数是对象释放系统资源的保障

第23课 - 神秘的临时对象

实验-临时对象

#include <stdio.h>class Test {int mi;//优化1位置
public:Test(int i) {mi = i;}Test() {//优化2位置//case0//Test(0);//case1//Test(2);//case2}void print() {printf("mi = %d\n", mi);}
};int main()
{Test t;t.print();return 0;
}
/*case0:
mi = 0case1:
mi = 0case2:
mi = 0
*/

代码优化

为避免使用临时对象,可在private内添加初始化函数
;在public的无参构造函数内调用

//优化1:void init(int i){mi = i;}
//优化2:
// init(0);//output:mi = 0init(1);//output:mi = 1

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

实验-编译器对临时对象的优化

#include <stdio.h>class Test
{int mi;
public:Test(int i)//带参数构造函数{printf("Test(int i) : %d\n", i);mi = i;}Test(const Test& t)//拷贝构造函数{printf("Test(const Test& t) : %d\n", t.mi);mi = t.mi;}Test()//无参构造函数{printf("Test()\n");mi = 0;}void print(){printf("mi = %d\n", mi);}~Test(){printf("~Test()\n");}
};Test func()
{return Test(20);
}int main()
{Test t = Test(10); // ==> Test t = 10;//理论上:先定义了临时对象,再调用拷贝构造函数赋值给t,//实际:被编译器优化了Test tt = func();  // ==> Test tt = Test(20); ==> Test tt = 20;t.print();tt.print();return 0;
}
/*
Test(int i) : 10
Test(int i) : 20
mi = 10
mi = 20
~Test()
~Test()
*/

小结

直接调用构造函数将产生一个临时对象
临时对象是性能的瓶颈,也是bug来源
C++编译器会尽力避开临时对象
实际开发中要人为避开临时对象

第24课 - 经典问题解析二

多个对象析构时,析构顺序与构造顺序相反

在这里插入图片描述

实验-多对象析构函数顺序

#include <stdio.h>class Member
{const char* ms;
public:Member(const char* s){printf("Member(const char* s): %s\n", s);ms = s;}~Member(){printf("~Member(): %s\n", ms);}
};class Test
{Member mA;Member mB;
public:Test() : mB("mB"), mA("mA")//初始化列表{printf("Test()\n");}~Test(){printf("~Test()\n");}
};Member gA("gA");//全局对象int main()
{Test t;return 0;
}
/*
Member(const char* s): gA
Member(const char* s): mA
Member(const char* s): mB
Test()
~Test()
~Member(): mB
~Member(): mA
~Member(): gA先全局对象,再初始化列表,后进入构造函数
*/
```c#include <stdio.h>class Member
{const char* ms;
public:Member(const char* s){printf("Member(const char* s): %s\n", s);ms = s;}~Member(){printf("~Member(): %s\n", ms);}
};class Test
{Member mA;Member mB;
public:Test() : mB("mB"), mA("mA")//初始化列表{printf("Test()\n");}~Test(){printf("~Test()\n");}
};Member gA("gA");//全局对象int main()
{Test t;return 0;
}
/*
Member(const char* s): gA
Member(const char* s): mA
Member(const char* s): mB
Test()
~Test()
~Member(): mB
~Member(): mA
~Member(): gA先全局对象,再初始化列表,后进入构造函数
*/

对于栈对象和全局对象,类似于入栈和出栈的顺序,最后构造的对象被最先析构。
堆对象的析构发生在使用delete的时候,与delete的使用顺序相关。

const修饰对象的特性
const修饰的对象为只读对象,其成员不允许被改变
只读对象是编译阶段的概念,运行时无效

C++中的const成员函数

  • const对象只能调用const的成员函数
  • const成员函数中只能调用const成员函数
  • const成员函数中不能直接改写成员变量的值
    在这里插入图片描述

实验-const成员函数

#include <stdio.h>class Test
{int mi;
public:
int mj;Test(int i);Test(const Test& t);/* int getMi();*//**/int getMi()const;
};Test::Test(int i)
{mi = i;
}Test::Test(const Test& t)
{
//调用普通函数会报错,只能调用const成员函数  t为const变量
mi=t.getMi();//mi=t.mi;}
/**/
int Test::getMi()const//const成员函数
{
//mi=2;//试试改值   error: assignment of member 'Test::mi' in read-only object
return mi;}
/*int Test::getMi()   //普通函数
{return mi;
}*/int main()
{const Test t(1);//printf("t.getMi()=%d\n",t.getMi());// (调用普通函数)error: passing 'const Test' as 'this' argument discards qualifiers [-fpermissive]printf("t.getMi()=%d\n",t.getMi());//t.getMi()=1(调用const函数)//t.mj=2;// error: assignment of member 'Test::mj' in read-only objectreturn 0;
}

成员函数和成员变量都是隶属于具体对象的吗

从面向对象的角度
对象由属性(成员变量)和方法()构成
从程序运行的角度
对象由数据和函数构成
数据可以位于栈,堆和全局数据区
函数只能位于代码段

在这里插入图片描述

实验-this的使用

#include <stdio.h>class Test
{int mi;
public:int mj;Test(int i);Test(const Test& t);int getMi();void print();
};Test::Test(int i)
{mi = i;
}Test::Test(const Test& t)
{mi = t.mi;
}int Test::getMi()
{return mi;
}void Test::print()
{printf("this = %p\n", this);
}int main()
{Test t1(1);Test t2(2);Test t3(3);printf("t1.getMi() = %d\n", t1.getMi());printf("&t1 = %p\n", &t1);t1.print();printf("t2.getMi() = %d\n", t2.getMi());printf("&t2 = %p\n", &t2);t2.print();printf("t3.getMi() = %d\n", t3.getMi());printf("&t3 = %p\n", &t3);t3.print();return 0;
}
/*
t1.getMi() = 1
&t1 = 000000000061FE18
this = 000000000061FE18
t2.getMi() = 2
&t2 = 000000000061FE10
this = 000000000061FE10
t3.getMi() = 3
&t3 = 000000000061FE08
this = 000000000061FE08*/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/11847.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring AI项目Open AI绘画开发指导

Spring AI项目创建 Spring AI简介创建Spring AI项目配置项目pom和application文件controller接口开发运行测试 Spring AI简介 Spring AI 是 AI 工程的应用框架。其目标是将 Spring 生态系统设计原则&#xff08;如可移植性和模块化设计&#xff09;应用于 AI&#xff0c;并推广…

【机器学习】机器学习与人工智能融合新篇章:自适应智能代理在多元化复杂环境中的创新应用与演进趋势

&#x1f512;文章目录&#xff1a; &#x1f4a5;1.引言 &#x1f68b;1.1 机器学习与人工智能的发展背景 &#x1f68c;1.2 自适应智能代理的概念与重要性 &#x1f690;1.3 研究目的与意义 ☔2.自适应智能代理的关键技术 &#x1f6e3;️2.1 环境感知与信息处理技术 …

RelationMap图谱--VUE,真实项目提供mock数据

RelationMap官网&#xff1a; 在线配置官网&#xff08;可以把数据放进去&#xff0c;直接看效果&#xff09; VUE2 效果&#xff1a;左侧列表栏&#xff0c;点击右侧显示对应的图谱 代码&#xff1a;按照代码直接贴过去&#xff0c;直接出效果 relationMap/index.vue <te…

泽攸科技无掩模光刻机:引领微纳制造新纪元

在当今科技迅猛发展的时代&#xff0c;微纳制造技术正变得越来越重要。泽攸科技作为这一领域的先行者&#xff0c;推出了其创新的无掩模光刻机&#xff0c;这一设备在微电子制造、微纳加工、MEMS、LED、生物芯片等多个高科技领域展现出了其独特的价值和广泛的应用前景。 技术革…

Python-VBA函数之旅-tuple函数

目录 一、tuple函数的常见应用场景 二、tuple函数使用注意事项 三、如何用好tuple函数&#xff1f; 1、tuple函数&#xff1a; 1-1、Python&#xff1a; 1-2、VBA&#xff1a; 2、推荐阅读&#xff1a; 个人主页&#xff1a; https://myelsa1024.blog.csdn.net/ 一、tu…

爱普生推出适用于物联网小尺寸温补晶振TG1612SLN

爱普生推出一款小尺寸温补晶振TG1612SLN&#xff0c;之前推出的小尺寸温补晶振TG2016SLN&#xff0c;封装2016已经是很小了&#xff0c;而TG1612SLN的尺寸仅为1.6x1.2x0.45毫米&#xff0c;不得不佩服爱普生的研发能力。 温度补偿晶体振荡器TG1612SLN使用爱普生开发和制造…

程序员的神奇应用:从代码创造到问题解决的魔法世界之持续集成/持续部署

文章目录 持续集成/持续部署 在软件开发的海洋中&#xff0c;程序员的实用神器如同航海中的指南针&#xff0c;帮助他们导航、加速开发、优化代码质量&#xff0c;并最终抵达成功的彼岸。这些工具覆盖了从代码编写、版本控制到测试和部署的各个环节。 在当今数字化的世界里&…

Llama 3 是怎么回事?Arena 数据分析

4 月 18 日,Meta 发布了他们最新的开放权重大型语言模型 Llama 3。从那时起,Llama 3-70B 就在 English Chatbot Arena 排行榜上迅速上升,拥有超过 50,000 次对战。Meta 的这一非凡成就对开源社区来说是个好消息。在这篇博文中,我们旨在深入探讨为什么用户将 Llama 3-70b 与 GPT…

Linux信息显示相关指令

1、查看cpu 查看cpu信息:cat /proc/cpuinfo 查看cpu个数:nproc cat /proc/cpuinfo | grep "physical id" | uniq | wc -l uniq命令:删除重复行;wc –l命令:统计行数 查看CPU核数 cat /proc/cpuinfo | grep "cpu cores" | uniq 2、查看内存 cat /pr…

快解析Tplink端口映射如何设置

Tplink作为国内知名路由器品牌&#xff0c;有着广泛的用户群体。使用快解析端口映射是实现内网服务器被外网访问必须要做的设置&#xff0c;很多对网络不懂得小白不知道该到哪里去做&#xff0c;下面我就讲解一下tplink路由器如何做端口映射。 1&#xff1a;访问路由器 &#…

uboot 顶层 Makefile 逐行分析

文章目录 0001-00080009-00180019-00510052-00920093-01070108-01230124-01770178-21350178-01810182-01860187-02020203-02450246-02620263-02720273-03370338-03830384-03870388-04250426-04490450-04740475-04860487-04980499-05340535-05500551-05650566-221822192220-2332…

想半天憋不出几个字?试试AI扩写

大家在写文章时是否也经常这样&#xff1f;想了半天&#xff0c;结果只能写出几个字&#xff0c;但是要求往往又是几百多个字&#xff0c;那么有没有啥工具可以帮我们在原文的基础上扩写一下文章字数&#xff0c;让我们达到字数要求呢&#xff1f; 下面给大家介绍一下如何扩写文…

Django开发实战之定制管理后台界面及知识梳理(下)

接上一篇&#xff1a;Django开发实战之定制管理后台界面及知识梳理&#xff08;中&#xff09; 1、前台设置 1、隐藏路由 当你输入一个错误地址时&#xff0c;可以看到这样的报错&#xff1a; 从这样的报错中&#xff0c;我们可以看到&#xff0c;这个报错页面暴漏了路由&a…

FullCalendar日历组件集成实战(1)

背景 有一些应用系统或应用功能&#xff0c;如日程管理、任务管理需要使用到日历组件。虽然Element Plus也提供了日历组件&#xff0c;但功能比较简单&#xff0c;用来做数据展现勉强可用。但如果需要进行复杂的数据展示&#xff0c;以及互动操作如通过点击添加事件&#xff0…

python数据可视化:从n个点中挑选m组3个点绘制m个三角形matplotlib.pyplot.triplot()

【小白从小学Python、C、Java】 【考研初试复试毕业设计】 【Python基础AI数据分析】 python数据可视化&#xff1a; 从n个点中挑选m组3个点 绘制m个三角形 matplotlib.pyplot.triplot() [太阳]选择题 以下关于matplotlib.pyplot.triplot()函数说法正确的是&#xff1f; impor…

Linux---windows 机器和远端的 Linux 机器如何通过 XShell 传输文件

一、关于rzsz 这个工具用于 windows 机器和远端的 Linux 机器通过 Xshell 传输文件. 二、下载rzsz软件 用root输入命令&#xff1a; sudo yum install -y lrzsz下载完成&#xff1a; 三、如何传输 有图形化界面 1、从Windows机器传输给远端Linux机器 ① 直接拖拽 直接将…

微软如何打造数字零售力航母系列科普10 - 什么是Azure Databricks?

什么是Azure Databricks&#xff1f; 目录 一、数据智能平台是如何工作的&#xff1f; 二、Azure Databricks的用途是什么&#xff1f; 三、与开源的托管集成 四、工具和程序访问 五、Azure Databricks如何与Azure协同工作&#xff1f; 六、Azure Databricks的常见用例是…

JavaSE——集合框架一(2/7)-Collection集合的遍历方式-迭代器、增强for循环、Lambda、案例

目录 Collection的遍历方式 迭代器 增强for循环&#xff08;foreach&#xff09; Lambda表达式遍历集合 案例 需求与分析 代码部分 运行结果 Collection的遍历方式 迭代器 选代器是用来遍历集合的专用方式&#xff08;数组没有选代器&#xff09;&#xff0c;在Java中…

【Spring Boot】 深入理解Spring Boot拦截器:自定义设计与实现全攻略

&#x1f493; 博客主页&#xff1a;从零开始的-CodeNinja之路 ⏩ 收录文章&#xff1a;【Spring Boot】 深入理解Spring Boot拦截器&#xff1a;自定义设计与实现全攻略 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 SpringBoot统⼀功能处理一…

第十五节:贪心算法(下)

一 、 贪心算法的解题套路实战一&#xff08;最多的会议宣讲场次&#xff09; 1.1 描述 一些项目要占用一个会议室宣讲&#xff0c;会议室不能同时容纳两个项目的宣讲。 给你每一个项目开始的时间和结束的时间 你来安排宣讲的日程&#xff0c;要求会议室进行的宣讲的场次最多。…