C++语言总结(二)
类和对象
C和C++中struct区别
- C语言struct只有变量
- C++语言中既有变量,也有函数
类的封装
定义一个结构体用来表示一个对象所包含的属性,函数用来表示一个对象所具有的行为,这样就表示一个事物,在C语言中,行为和属性是分开的,也就是说吃饭这个属性不属于某类对象,而属于所有共同的数据,在调用的时候就有可能发生问题。
我们应该属性和行为应该放开一起,一起表示一个具有属性和行为的对象。
封装特性包含两个方面,一个是属性和变量合成一个整体,一个给属性和函数增加访问权限。
封装
- 把变量(属性)和函数(操作)合成一个整体,封装在一个类中
- 对变量和函数进行访问控制
访问权限
- 在类的内部(作用域范围内),没有访问权限之分,所有成员可以相互访问
- 在类的外部(作用域范围外),访问权限才有意义:public,private,protected
- 在类的外部,只有public修饰的成员才能被访问,在没有涉及继承和派生时,private和protected是同等级的,外部不允许访问
struct和class的区别
class默认的访问权限是private,struct默认访问权限为public
将成员变量设置为private
如果成员变量不是public,客户端唯一能够访问对象的方法就是通过成员变量函数
如果类中所有的public权限的成员都是函数,客户在访问类成员时只会默认访问函数
构造函数和析构函数
构造函数主要作用在于创建对象是对成员属性赋值,构造函数由编译器自动调用,无需手动编译
析构函数主要用于对像销毁前系统自动调用,执行一些清理工作
构造函数语法:
- 构造函数函数名和类名相同,没有返回值,不能有void,但可以有参数
- ClassName(){}
析构函数语法:
- 析构函数名是在类名前面加”~”组成,没返回值,不能有void,不能有参数,不能重载
- ~ClassName(){}
构造函数的分类及调用
- 按参数类型:分为无参构造函数和有参构造函数
- 按类型分类:普通构造函数和拷贝构造函数(复制构造函数)
拷贝构造函数的调用时机
- 对象以值传递的方式给函数参数
- 函数局部对象以值传递的方式从函数返回(vs debug模式下调用一次拷贝构造,qt不调用任何构造)
- 用一个对象初始化另一个对象
构造函数调用规则
- 默认情况下,C++编译器至少为我们写的类增加3个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对类中非静态成员属性简单值拷贝
- 如果用户定义拷贝构造函数,C++不会再提供任何默认构造函数
- 如果用户定义了普通构造(非拷贝),C++不在提供默认无参构造,但是会提供默认拷贝构造
深拷贝和浅拷贝
浅拷贝
同一类型的对象可以赋值,使得两个对象的成员变量的值相同,两个对象仍然是独立的两个对象,这种情况被称为浅拷贝.
一般情况下,浅拷贝没有任何副作用,但是当类中有指针,并且指针指向动态分配的内存空间,析构函数做了动态内存释放的处理,会导致内存问题。
深拷贝
当类中有指针,并且此指针有动态分配空间,析构函数做了释放处理,往往需要自定义拷贝构造函数,自行给指针动态分配空间,深拷贝。
多个对象构造和析构
初始化列表
构造和其他函数不同,除了有名字,参数列表,函数体之外还有初始化列表。
初始化列表简单使用:
class Person{
public:
Person(int a, int b, int c):mA(a),mB(b),mC(c){}
private:
int mA;
int mB;
int mC;
类对象作为成员
在类中定义的数据成员一般都是基本的数据类型,但是类中的成员也可以是对象,叫做对象成员。
C++中对对象的初始化是非常重要的操作,当创建一个对象的时候,C++编译器必须确保调用了所有子对象的构造函数,如果所有的子对象有默认构造函数,编译器可以自动调用他们,但是如果子对象没有默认的构造函数,或者想指定调用某个构造函数怎么办?
解决办法非常简单:对于子类调用构造函数,C++为此提供了专门的语法,即构造函数初始化列表。
当调用构造函数时,首先按各对象成员在类定义的顺序(和参数列表的顺序无关)依次调用它们的构造函数,对这些对象初始化,最后调用本身的函数体,也就是说,先调用对象成员的构造函数,再调用本身的构造函数。
析构函数和构造函数调用顺序相反。
explicit关键字
C++提供了关键字explict,禁止通过构造函数进行隐式转换。声明为exclicit的构造函数不能在隐式转换中使用。
注意
- explict用于修饰构造函数,防止隐式转换
- 是针对单参数的构造函数(或者除了第一个参数外其余都有默认值的多参构造)而言。
动态对象创建
当我们创建数组的时候,总是需要提前预定数组的长度,然后编译器分配预定长度的数组空间,在使用数组的时候,会有这样的问题,数组也许空间太大,浪费空间,也许空间不足,所以对于数组来讲,如果能根据需要来分配空间大小最好不过
对象创建
当创建一个C++对象时会发生两件事:
1.为对象分配内存
2.调用构造函数来初始化那块内存
第一步我们能够保证实现,需要我们确保第二步一定能发生。C++强迫我们这么做是因为使用未初始化的对象是程序出错的一个重要原因
C动态分配内存方法
为了在运行时动态分配内存,c在他的标准库中提供了一些函数,malloc以及它的变种calloc和realloc,释放内存的free,这些函数是有效的、但是原始的,需要程序员理解和小心使用。
在使用malloc时必须注意以下问题:
- 程序员必须确定对象的长度
- malloc返回一个void*指针,C++不允许void*赋值给其他任何指针,必须强转。
- malloc可能申请内存失败,所以必须返回值来确保内存分配成功
- 用户在使用对象之前必须记住对他初始化,构造函数不能显示调用初始化(构造器函数是由编译器调用),用户有可能忘记调用初始化函数
**C的动态内存分配函数太复杂,容易令人混淆,是不可接受的,C++中我们推荐使用运算符new和delete
new
C++中解决动态内存分配的方案是把内存创建一个对象所需要的操作都结合在一起称为new的运算符里。当new创建一个对象时,它就在堆里为对象分配内存并调用构造函数完成初始化。
new操作符能确定在调用构造函数初始化之前内存分配是成功的,所有不用显示确定调用是否成功。
现在我们发现在堆里创建对象的过程变得简单了,只需要一个简单的表达式,它带有内置的长度计算、类型转换和安全检查。这样在堆创建一个对象和在栈里面创建一个对象一样简单。
delete
new表达式的反面是delete表达式。delete表达式先调用析构函数,然后释放内存。
正如new表达式返回一个指向对象的指针一样,delete需要一个对象的指针
delete只适用于new创建的对象
如果使用一个malloc或者calloc或者relloc穿件的对象使用delete,这个行为是未定义的,应为大多数new和delete 的实现机制都使用malloc和free,所以很可能没有调用析构函数就释放了内存。
如果使用一个malloc或者calloc或者realloc创建的对象使用delete,这个行为是未定义的,应为大多数new和delete的实现机制都使用了malloc和free,所以很可能没有调用析构函数就释放了内存。
如果删除的对象的指针是NULL将不会发生任何事情,因此建议在删除指针后,立即把指针赋值为NULL,以免对它删除两次,对一个对象删除两次可能会产生某些问题。
当创建一个对象数组的时候,必须对数组中的每一个对象调用构造函数
如果对一个void*指针执行delete操作,这将可能成为一个程序错误,除非指针指向的内容是非常简单,因为它将不执行析构函数,
new的时候发生了两件事:一、分配内存;二、调用构造函数
delete调用的时候:一、析构函数;二、释放内存
如果在new表达式中使用[],必须在相应的delete表达式中也使用[],如果在new表达式中不使用[],一定不要在相应的delete[]表达式中使用[]
静态成员
在类定义中,他的成员(包括成员变量和成员函数),这些成员可以用关键字static声明为静态的,称为静态成员。
不管这个类创建了多少个对象,静态成员只有一个拷贝,这个拷贝被所有属于这个类的对象共享。
在一个类中,若将一个成员变量声明为static,这种成员称为静态成员变量,与一般的数据成员不同,无论建立多少个对象,都只有一个静态数据的拷贝。静态成员变量,属于某个类,所有的对象共享,
静态变量,是在编译阶段就分配空间,对象还没有创建时,就已经分配空间
- 静态成员变量必须在类中声明,在类外定义
- 静态数据成员不属于某个对象,在为对象分配空间中不包括静态成员所占空间
- 静态数据成员可以通过类名或者对象名来引用。
在类定义中,前面有static说明的成员函数称为静态成员函数。静态成员函数使用方式和静态变量一样,同样在对象没有创建前,即可通过类名调用。静态成员函数主要是为了访问静态成员变量,但是不能访问普通成员变量。
静态成员函数的意义,不在于信息共享,数据沟通,而在于管理静态数据成员,完成对静态数据成员的封装
- 静态成员函数只能访问静态变量,不能访问普通成员变量
- 静态成员函数的使用和静态成员变量一样
- 静态成员函数也有访问权限
- 普通成员函数可访问静态成员变量、也可以访问非静态成员变量
const静态成员属性
如果一个类的成员,既要实现共享,又要实现不可改变,那就使用static const修饰,定义静态const数据成员时,最好在类的内部初始化。
单例模式
单例模式是一种常见的软件设计模式,在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约资源。如果希望在系统中某个类的对象只存在一个,单例模式是最好的解决方案。
SingLeton:在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以访问他的唯一的实例;防止在外部对其实例化,将其默认构造函数和拷贝构造函数设计为私有;在单例内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例。
class Printer{
public:
static Printer *getINstance(){return pPrinter;}
void PrintText(string text)
{
cout << "打印内容" << text << endl;
cout << "已打印次数" << mTimes << endl;
cout << "-----------" << endl;
mTimes++;
}
private:
Printer(){mTimes = 0;}
Printer(const Printer&){}
static Printer* pPrinter;
int mTimes;
};
Printer Printer::mPrinter = new Printer;//类内声明,类外定义
void test()
{
Printer *printer = Printer::getInstance();
Printer->PrinterText("离职报告");
Printer->PrinterText("入职合同");
Printer->PrinterText("提交代码");
}