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());//0
printf("gt.j = %d\n", gt.getJ());//0
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;
}
/*
E:\test>g++ 17-1.cpp
E:\test>a
gt.i = 0
gt.j = 0
t1.i = 14380624
t1.j = 0
pt->i = 39267488
pt->j = 0
E:\test>a
gt.i = 0
gt.j = 0
t1.i = 1928784
t1.j = 0
pt->i = 6499488
pt->j = 0
E:\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());//1
printf("gt.j = %d\n", gt.getJ());//2
Test 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());//1
printf("pt->j = %d\n", pt->getJ());//2
delete 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的地址不一样了
注意:自定义拷贝构造函数,必然需要实现深拷贝
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 initialized
const 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): 0
while( ++i < 10 )
if( i % 2 )
new Test(i); // Test(int i): 1, 3, 5, 7, 9
if( i < 4 )
new Test(*a1);
else
new Test(100); // Test(int i): 100
return 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 = 0
case1:
mi = 0
case2:
mi = 0
*/
代码优化
为避免使用临时对象,可在private内添加初始化函数
;在public的无参构造函数内调用
//优化1:
void init(int i)
{
mi = i;
}
//优化2:
// init(0);//output:mi = 0
init(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 object
return 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
*/