工程比较难调试的bug
1)野指针 临时对象
2)半成品对象
#成员变量的初始值
栈上创建的成员变量,成员变量初始化值为随机值
堆上创建的成员变量,成员变量的初始值也是随机值
静态区(全局变量)创建的成员变量,成员的初始值为0
封装的概念
不是全部的属性跟行为都要给人知道,将类的属性隐藏起来
封装(作用于)-》引入了成员变量和成员函数 =》可以给定义访问级别
类的实现细节都在.cpp,接口(类的使用方式)在,h
类与类间的关系:
组合
继承:
友元: friend ,单项,可以访问具体类里的所有成员
打破关系,尽量少用,现代工程基本被弃用
继承属性:
child\parent public protected privice
public public protected privice
protected protected protected privice
privice privice privice privice
虚函数: virtual
#对象的构造顺序
局部对象的构造取决于程序流顺序
堆对象的创建取决于new
全局对象的构造取决于编译器
#构造函数 实现了对象的出厂初始化
特点:
类的构造函数用于对象的初始化
构造函数没有返回值
对象的创建(定义),构造函数自动调用
构造函数可以带参数 =>支持函数重载
特殊的构造函数
无参构造函数 IntArray() 当不存在构造函数时,编译器会自动创建一个无参构造函数
拷贝构造函数 IntArray(const IntArray& obj) =>为了兼容C的初始化方式 s= s1;
浅拷贝: 编译器自动创建的时候会进行浅拷贝 ==>拷贝后的物理状态一样 都是指向同一个地址的内容
浅拷贝的问题 t1 =3; t2 =t1
那么t1 跟t2 都是指向了3的内存,那么释放t1对象后,在释放t2会造成内存溢出q
深拷贝:自己编写的拷贝构造函数都要深拷贝 ==>拷贝后的逻辑状态一致
1)成员指向了动态内存空间 2)成员打开了外存的问题 3)使用了系统的网络端口
工程比较难调试的bug
1)野指针 临时对象
2)半成品对象 (那些内存问题,仅仅产生一次bug)
#构造函数注意点1:二阶构造模式
构造函数的弱点: =>引出二阶构造模式
如何判断构造函数的执行结果? --->无法判断
在构造函数中执行return会怎么样? -->可以提前return,会立即返回
构造函数执行结果是否意味着对象构造成功? -->不一定,创建对象时会自动调用构造函数进行出厂化设置,但如果构造函数内部出现了问题,(初始化状态错误)但不会影响到对象的创建=>成了一个半成品对象(半成品对象(不是预期的结果)在c++是合法,但在现实中都是bug的来源)
class Test
{
int mi;
int mj;
bool mStatus; //为了弥补return提前返回初始化状态出错这个问题,唐老师粗方案:强行加判断
public:
Test(int i, int j) : mStatus(false) 初始化列表
{
mi = i;
return; //执行完这句直接返回,再也不会往下走
mj = j;
mStatus = true;
}
...
int status() //引入功能函数
{
return mStatus;
}
}
int main()
{
Test t1(1, 2);
if( t1.status() ) //但缺点:每次创建对象都要判断这个对象是否完整
{
printf("t1.mi = %d\n", t1.getI());
printf("t1.mj = %d\n", t1.getJ());
}
return 0;
}
构造函数漏洞哦总计:
1)只提供自动初始化的机会,不能保证初始化状态一定成功,有机会出现半成品对象
2)执行return语句构造函数立刻结束
真正解决方案:二阶构造模式
第1阶段:资源无关的初始化操作
第二阶段:使用系统资源的操作--->可能出现异常的情况(内存(new 堆空间),打开文件,)
#ifndef _INTARRAY_H_
#define _INTARRAY_H_
class IntArray
{
private:
int m_length;
int* m_pointer;
IntArray(int len); //构造函数作为第一阶段构造函数,里面存放资源无关的初始化操作
IntArray(const IntArray& obj);
bool construct(); //普通的bool类型成员函数作为第二阶段构造哈市,进行申请资源相关的操作
public:
static IntArray* NewInstance(int length); //由于二接构造函数是放在private,类的外部无法直接访问,于是在public定义一个静态成员函数 作为对象创建函数 返回一个IntArray* (对象指针)
int length();
bool get(int index, int& value);
bool set(int index ,int value);
~IntArray();
};
#endif
#include "IntArray.h"
IntArray::IntArray(int len)
{
m_length = len;
}
bool IntArray::construct()
{
bool ret = true;
m_pointer = new int[m_length];
if( m_pointer )
{
for(int i=0; i<m_length; i++)
{
m_pointer[i] = 0;
}
}
else
{
ret = false;
}
return ret;
}
IntArray* IntArray::NewInstance(int length)
{
IntArray* ret = new IntArray(length); //堆空间创建对象,,执行构造函数,第一阶段构造函数
//ret 判断堆空间有没有足够内存创建这个对象
//ret->construct()) 判断第二阶资源申请是否成功
if( !(ret && ret->construct()) )
{
delete ret;
ret = 0;
}
return ret;
}
`
#构造函数注意点2:临时对象(直接调用构造函数)
构造函数是否可以直接调用? 可以,必要时我们可以手工调用
构造函数中再调用构造函数是否可以? 语法可以,但构造函数中的构造函数只有1条语句的生命周期,之后就会被是否
直接调用构造函数的行为是?生成一个临时对象(C++的灰色地带)
临时对象的特点:
临时对象的生命周期只有一条语句的时间
临时对象的作用域只在一条语句中
临时对象是c++的灰色地带 ==>严重等级:等价于c语言中的野指针
```cpp
代码案例:
本义是想利用代码复用先调用Test(),后在该Test里面调用Test(0)初始化,但输出了随机值
原因解释 Test()里的Test(0)生成了一个临时对象,只在该作用域有效,生命周期只有一条语句,等价于Test(){}
class Test {
int mi;
public:
Test(int i) {
mi = i;
}
Test() {
Test(0); //构造函数里调用构造函数
}
void print() {
printf("mi = %d\n", mi);
}
};
唐老师给出的工程方法-->避免临时对象的产生
class Test2 {
int mi;
int init(int i)
{ mi=i; }
public:
Test(int i) {
init(i);
}
Test() {
init(0); //构造函数里调用构造函数
}
void print() {
printf("mi = %d\n", mi);
}
};
int main()
{
Test t;
Test2 t2;
Test2();
Test2(100);
t.print();
y
return 0;
}
#概念
-
对象的定义与声明不同
对象的定义: //会自动调用构造函数
Test op;
对象的声明:
extern Test op1; -
对象的初始化与赋值不同
初始化:对正在创建的对象进行
赋值:对生成的对象进行
#初始化列表
1)类成员的初始化顺序与成员的声明顺序有关而与初始化列表的位置顺序无关
2)初始化先与构造函数的函数体执行
class init
{
private:
const int ci;
Value m2;
Value m3;
Value m1;
public:
Test() : m1(1), m2(2), m3(3), ci(100)
{
printf("Test::Test()\n");
}
#const成员
1)类中可以定义const成员变量,成员变量为只读变量(不能出现在赋值符的左边)
(const 变量会分配空间,且跟随类对象,如果类对象是在栈空间,那么也在栈空间)
2)const成员变量必须在初始化列表中指定初值
(初始化工作在初始化列表完成,因此编译器(编译阶段)无法直接得到const成员的初始值,因此无法进入符合表成为真正的常量,可以通过解引用后用指针重新赋值)
定义的是真正的常量,存储在符号表中
class point
{
const int ci;
public:
point():ci(10)
{
}
int setCI(int v)
{
int* p =const_cast<int*>(&ci); //解引用
*p =v;
}
};
析构函数 //完成销毁对象前的清理工作
1)无参函数,无返回类型
2)销毁对象,自动调用
什么时候需要自己定义析构函数?
1)自定义构造函数(拷贝构造函数),在构造函数中(有用到系统资源:如内存申请,文件打开等)
class IntArray
{
private:
int m_length;
int* m_pointer;
public:
IntArray(int len); //构造函数,无返回值类型
IntArray(const IntArray& obj); //拷贝构造函数
int length();
bool get(int index, int& value);
bool set(int index ,int value);
~IntArray(); //析构函数,无参数,无返回值类型
};
#类的静态成员变量 (static 修饰)
1)类的所有对象都可以共享静态成员变量(隶属于整个类)
2)类的静态成员变量不占用类的内存空间,位于全局数据区(data段)
3)静态成员变量的生命期未程序运行期
#静态成员函数
1)静态成员函数属于特殊成员函数,定义时不用写static,但要写类名
2)类的所有对象都可以共享静态成员函数(隶属于整个类)
3)可以通过对象名访问公有(public)静态成员函数
4)与普通函数的特别之处:
- 可以通过类名访问公有(public)静态成员函数
- 没有隐藏的this指针
-
静态成员函数不能访问普通成员变量(函数)? 3.1).静态成员函数只属于类本身,随着类的加载而存在,不属于任何对象,是独立存在的 3.2).非静态成员当且仅当实例化对象之后才存在,静态成员函数产生在前,非静态成员函数产生在后,故不能访问 3.3.内部访问静态成员用self::,而访问非静态成员要用this指针,静态成员函数没有this指针,故不能访问 原文解释链接:https://blog.csdn.net/kunlcw/article/details/82971292
class Test
{
private:
int err;
static int cCount; //静态成员变量
public:
Test()
{
cCount++;
}
~Test()
{
--cCount;
}
static int GetCount() //静态成员函数
{
err =8; // 这个是错误的,因为静态成员函数没有this指针,所以不能 this->err =8
return cCount;
}
};
**
友元的概念
**
1)单项,关键词: friend,无传递性
2)友元不受访问级别限制,可以直接访问类的所有成员
3)类的友元可以是其他类或具体函数
4)兼顾c的高效,但破坏了c++的封装性,现代工程(逐渐被弃用)
5)友元函数是一个普通的函数
友元普通函数在实现时,不需要类名的限定;在调用时,也不需要由实例来调用
你只想让A有权限访问B::data,而不允许C和D或者其他类访问B::data,这时候友元就有用了。
只有被B类声明为friend的类才可以访问B类的私有成员。
class ClassC
{
const char* n;
public:
ClassC(const char* n)
{
this->n = n;
}
friend class ClassB;//B是C的友元,B的所有成员可以直接访问C的所有成员,不受private访问限制
};
class ClassB
{
const char* n;
public:
ClassB(const char* n)
{
this->n = n;
}
void getClassCName(ClassC& c)
{
printf("c.n = %s\n", c.n);
}
friend class ClassA;
};
``