起初刚学C++时,很不习惯用new,后来看老外的程序,发现几乎都是使用new,想一想区别也不是太大,但是在大一点的项目设计中,有时候不使用new的确会带来很多问题。当然这都是跟new的用法有关的。new创建类对象,使用完后需使用delete删除,跟申请内存类似。所以,new有时候又不太适合,比如在频繁调用场合,使用局部new类对象就不是个好选择,使用全局类对象或一个经过初始化的全局类指针似乎更加高效。
c++的类/对象
类,是一种特殊的自定义类型,在实例化为对象时,会自动对成员进行初始化
1.类/对象的一些特性
对象的创建细节
当一个类实例化为一个对象时,会为其中的成员分配内存空间(所有成员默认private)。其中,成员变量集中放在对象的内存中(就和结构体一样),而成员函数是同一个类的所有对象公用的,单独定义在代码段,并不以函数指针的形式放在对象的内存中,由编译器在编译时直接链接到调用处
然后编译器连它们的初始化都给包办了,自动进行赋值,编译器先执行初始化列表,再执行构造函数,来为成员变量赋初始值
所以可以认为,从同个类中实例化而来的对象,它们的成员函数实质是同一个函数(入口地址相同)。那么问题来了,众所周知,成员函数可以直接访问成员变量,但是从同个类中实例化而来的对象,其成员函数如何区分自己和别人的成员变量呢?答案是隐藏的参数“this”,this的本质是一个指向当前对象自己的指针
class Test
{
int mi;
public:
Test(int i){ /*成员函数中隐藏的参数this,Test(int i, this)*/
mi = i; /*实际执行的是this->mi = i;*/
};
};
int main()
{/*t1和t2是不同的对象,但同一个类,它们的构造函数Test(int i)实质是同一个函数,那么Test(int i)中如何确定mi是t1.mi还是t2.mi呢?*/
Test t1(1);
Test t2(2);
}1234567891011121314
成员变量的访问权限
类中的所有成员默认为private,而成员变量的访问权限一般都应设为private/protected
private权限范围是当前类(即可被当前类中的成员函数直接获取),protected权限范围是当前类和子类(即可被当前类和子类中的成员函数直接获取)。注意它们的范围是类,而不是对象
所以说,同一个类的不同对象,它们的成员函数是可以互相访问对方的成员变量的,一般用引用参数来实现:
class Test
{
int var;
public:
void Test(const Test &ori)
{
cout << ori.var << endl; //可用这种方式直接访问同类的成员变量
cout << var << endl; //对于自己的成员变量则可直接访问,这的var本质是this —> var
}
};
int main()
{
Test t1;
Test t2;
t1.test(t2);
return 0;
}12345678910111213141516171819
静态成员函数与成员变量
静态成员函数与成员变量,它们的性质很类似于普通函数与普通全局变量
通过前面代码不难得知,成员函数依赖于对象传入的参数“this”,所以只能以对象名.函数名()的形式来调用。如果没有实例化对象,是不能单独去调用成员函数的。如果我们实在要单独调用,则可以使用“静态成员函数”,只需用static修饰成员函数,就可以使它不依赖于对象而被单独调用,可以在任何地方以类名::函数来访问。但是这么做的代价就是,由于没有参数“this”,“静态成员函数”无法访问对象中的成员变量,只能去访问类中公用的成员变量,即“静态成员变量”
所谓“静态成员变量”,即使用static修饰成员变量,并将其在全局定义。它会被分配到全局数据区,生命周期为永久,所有的对象将共用这个成员变量(类似成员函数的共用)。“静态成员变量”和全局变量唯一不同的是,它可以是private的,相对于全局变量更加安全,同时更低耦合高聚态;但是如果设置为public,可以在任何地方以类名::变量名来访问,那就和全局变量没有任何区别了…..
C++的概念:例如我说“车”的时候,你想到了什么?是不是车子都可以开,都有轮子.那么你头脑中自然就想起了一个车子类(类是属性的集合,车子的属性:可以开,有重量,大小,颜色).但是我说:“你看,那辆车多漂亮!”当你顺着我指着的方向看去,这就是一个实际存在的对象—————具体的车子.综上所述,类是事物的抽象.类的对象就是类的具体化,实际化.有“人"这个类.如果我用 人:张三.那么我就用人这个类去建立了张三这个"人"的对象.
理解完这段话,类、对象.是不是很简单呢?
class Test
{
static int cCount;
};
int Test::cCount = 0;//必须要在全局定义并初始化,这样编译器就会为其在全局分配空间123456
2.成员变量的初始化
构造函数
构造函数是一种特殊的成员函数,要求和类同名、无返回值。它将在类实例化时被自动调用,也可当作成员函数在外面手动调用
构造函数也是可以重载的,当类实例化时可以向构造函数传参,可以根据参数,选择成员变量初始化配置并赋值
在未指定任何构造函数的情况下,编译器默认提供一个空的无参构造函数,这样成员变量的初始值是不定的。一但指定了任意的构造函数,默认的无参构造函数立即失效,所以,假如我们定义了一个有参构造函数,实例化时却未传参,编译器会提示找不到无参构造函数
拷贝构造函数是一种特殊的构造函数,要求和类同名、参数为“const 类名&”,在该函数中,我们应该实现类中成员变量的拷贝
class Test
{
private:
int i;
public:
void func1(void);
Test()/*构造函数可以重载*/
{
i = 1;
}
Test(int v)
{
swich(v)
case xxxx
/*可以根据参数v,来选择不同的初始化配置*/
}
Test(const Test& t)/*一个简单的拷贝构造函数*/
{
i = t.i;
}
};
int main(void)
{
Test t0;//自动触发构造函数Test()
Test t1(123);//自动触发构造函数Test(int v)
Test t2 = 123;
Test t3 = Test(123);//极不推荐这种写法,这会产生临时对象
Test t4 = t1;//自动触发拷贝构造函数
Test t5(t1);
}1234567891011121314151617181920212223242526272829303132
若未提供拷贝构造函数,则编译器也默认提供一个拷贝构造函数,它会进行所有成员变量的无脑拷贝,这称之为“浅拷贝”,然而一些“动态申请的资源”也会原封不动的拷贝,它们经过“浅拷贝”后再去释放则极易发生错误,如下:
class Test
{
private:
int *p;
public:
Test()
{
p = new int;
}
void free(void)
{
delete p;
}
};
int main(void)
{
Test t0;//触发构造函数Test()
Test t1 = t0;//触发默认的拷贝构造函数
t0.free();
t1.free();//重复释放了空间,报错
}12345678910111213141516171819202122
故类中涉及到“动态申请的资源”:如堆内存、文件描述符、网络端口。千万别用默认的拷贝构造函数,保险起见还是自己定义一个拷贝构造函数,在里面进行进行申请资源
初始化列表
初始化列表会在构造函数之前进行成员变量的初始化,并且可以为const成员变量赋初始化值、类成员变量传参,这些事情构造函数都无法做到
尤其注意的是,初始化顺序与表中的顺序无关,只与类中成员声明的顺序有关。在程序开发中,这一点往往会引发大量的bug
class Value
{
private:
int mi;
public:
Value(int i)
{
printf("i = %d\\n", i);
mi = i;
}
};
class Test
{
private:
const int ci;
Value m2;
Value m3;
Value m1;
public:
/*实际的初始化顺序为ci、m2、m3、m1,如果这些成员变量的初始化顺序有讲究的话,这里必须特别注意*/
Test() : m1(1), m2(2), m3(3), ci(100)
{
/*构造函数里根本无法完成const成员变量初始化、类成员变量带传参的初始化*/
}
};1234567891011121314151617181920212223242526
C++用new和不用new创建类对象区别
一、new创建类对象与不new区别
下面是自己总结的一些关于new创建类对象特点:
new创建类对象需要指针接收,一处初始化,多处使用
new创建类对象使用完需delete销毁
new创建对象直接使用堆空间,而局部不用new定义类对象则使用栈空间
new对象指针用途广泛,比如作为函数返回值、函数参数等
频繁调用场合并不适合new,就像new申请和释放内存一样
二、new创建类对象实例
1、new创建类对象例子:
CTest* pTest = new CTest();
delete pTest;
pTest用来接收类对象指针。
不用new,直接使用类定义申明:
CTest mTest;
此种创建方式,使用完后不需要手动释放,该类析构函数会自动执行。而new申请的对象,则只有调用到delete时再会执行析构函数,如果程序退出而没有执行delete则会造成内存泄漏。
2、只定义类指针
这跟不用new申明对象有很大区别,类指针可以先行定义,但类指针只是个通用指针,在new之前并为该类对象分配任何内存空间。比如:
CTest* pTest = NULL;
但使用普通方式创建的类对象,在创建之初就已经分配了内存空间。而类指针,如果未经过对象初始化,则不需要delete释放。
3、new对象指针作为函数参数和返回值
下面是天缘随手写一个例子,不太严谨。主要示意一下类指针对象作为返回值和参数使用。
class CTest { public: int a; }; class CBest { public: int b; }; CTest* fun(CBest* pBest) { CTest* pTest = new CTest(); pTest->a = pBest->b; return pTest; } int main() { CBest* pBest = new CBest(); CTest* pRes= fun(pBest); if(pBest!=NULL) delete pBest; if(pRes!=NULL) delete pRes ; return -1; }
3.创建对象的顺序
由于类在实例化为对象时,会自动初始化成员,并且初始化采用的参数可以是其它对象。而当把对象定义成全局变量时,对象之间的互相依赖是十分危险的,因为相对于局部变量,全局变量的定义先后顺序是未知的。同样的道理,在多线程中也要小心
class Test
{
private:
int mi;
public:
Test(int i)
{
mi = i;
}
Test(const Test& obj)
{
mi = obj.mi;
}
};
//假设这两个对象是全局变量
Test a1 = 1;//编译器有可能先定义a2,再定义a1,程序运行就会出现错误
Test a2 = a1;1234567891011121314151617
所以,我们在实例化类时,要注意以下几点
尽量减小对象间的耦合度
尽量别用全局变量
在多线程中警惕对象间的耦合
4.对象的销毁
所谓被销毁,就是对象所占的内存被释放。若对象定义为局部变量,它会在函数结束时被销毁;若对象定义在堆内存上,它会在堆内存释放时被销毁;若对象定义为全局变量,那自然就不会被销毁
对象在被销毁时,会自动调用析构函数,它是一种特殊的成员函数,要求和类同名(前面加~)、无返回值、无参。尤其注意的是,析构函数是唯一的,不可重载,专门用来释放构造函数申请的资源
class Test
{
private:
int *p;
public:
Test()
{
p = new int; //构造函数一般用来申请资源
}
~Test()
{
delete p; //析构函数和构造函数成对出现,释放构造函数申请的资源
}
};1234567891011121314
不论对象定义为局部变量还是全局变量,当作用域结束时,其销毁(析构)的顺序与其实例化(构造)的顺序相逆,类似“倒影式结构”
神秘的临时对象
普通的成员函数,它们可以在类的内部互相调用。但是在类内部调用构造函数,会产生一个神秘的临时对象,所以千万不能再类内部调用构造函数
class Test {
int mi;
public:
Test(int i) {
mi = i;
}
Test() {
Test(0);//这里会诞生一个临时对象,赋值操作并不会作用到当前对象
}
};
int main(void)
{
Test t;
}123456789101112131415
临时对象的生命周期和作用域只限于一条语句中,临时对象和野指针一样,是需要特别警惕的灰色地带。在类内部,普通的成员函数之间可以随意调用,并不会产生临时对象
5.二阶构造模式
当一个类实例化时,若构造函数在初始化成员变量时,进行申请资源操作而没有得到理想结果(比如new一个变量失败),这时诞生的对象就称为“半成品对象”。这是一种危害很大的bug,因为资源申请并不是每次都会失败,所以将导致bug很难复现
构造函数中,成员变量的初始化可以分为两类:资源无关操作、申请资源操作(内存、文件描述符、网络端口)。其中,申请资源操作可能会出现异常
二阶构造模式可以解决这个问题:将构建过程分为两步,第一阶段负责资源无关操作;第二阶段负责申请资源操作,并判断是否成功,若失败则删除“半成品对象”
class TwoPhaseCons
{
private:
int mi;
TwoPhaseCons(int i) // 第一阶段构造函数,设为私有
{
//在这里进行资源无关操作
mi = i;
}
bool construct() // 第二阶段构造函数,设为私有
{
//在这里进行申请资源操作
return true;
}
public:
static TwoPhaseCons* NewInstance(int i); // 对象创建函数,设为公开
};
TwoPhaseCons* TwoPhaseCons::NewInstance(int i)
{
//在对象创建函数中,创建对象,触发第一阶段构造函数
TwoPhaseCons* ret = new TwoPhaseCons(i);
// 若第二阶段构造失败,返回 NULL
if( !(ret && ret->construct()) ) {
delete ret;
ret = NULL;
}
return ret;
}
int main()
{
//利用对象创建函数,来获得指向对象的指针,从而实现对象的创建
TwoPhaseCons* obj = TwoPhaseCons::NewInstance(1);
if(obj == NULL){
return -1;
}
//到这为止,整个对象就构建完了
delete obj;
return 0;
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546
如此一来,即可完美避免“半成品对象”的产生
C++类的定义:类是定义同一类所有对象的变量和方法的蓝图或原型。类也可以定义类变量和类方法。可以从类的实例中或者直接从类中访问类变量和方法。类方法只能操作类变量,不必访问实例变量或实例方法。系统在第一次在程序中遇到一个类时为这个类建立它的所有类变量的拷贝,这个类的所有实例共享它的类变量。 C++对象的定义:定义一个类就相当于创建了一个新的class类型。要使用类,还必须用已经定义的类去说明它的实例变量(即对象)。在C++中,class类型一旦被定义,它的实例变量(对象)就能被创建,并初始化,且能定义指针变量指向它。实例化的类就是对象。 类和对象其实是C语言里的结构体的延伸,在C++里,struct 和 class其实本质上是一样的,唯一区别就是struct默认是公开的,class默认是私有的,公开时需要声明为public;还有就是C语言里的结构体,不能包含函数和继承,C++可以包含函数以及继承其他的类。
¥299.00
¥399.00
¥29.00
¥498.00