类和对象
1、类和对象的基本概念
1.1C和C++中struct的区别
- C语言struct只有变量
- C++语言struct既有变量,也有函数
1.2类的封装
现实世界的事物所具有的共性就是每个事物都具有自身的属性,一些自身具有的行为;所以如果我们能把事物的属性和行为全都表示出来,也就可以抽象出来这个事物。
对象封装特性包含两个方面:一是属性和行为合成一个整体;二是给属性和行为增加访问权限。
1.2.1封装
- 1.把变量(属性)和函数(操作)合成一个整体,封装在一个类中
- 2.对变量和函数进行访问权限控制
1.2.2访问权限
- 1.在类的内部(作用域范围内),没有访问权限之分,所有成员可以相互访问
- 2.在类的外部(作用域范围外),访问权限才有意义:public private protected
- 3.在类的外部,只有public修饰的成员才能被访问,在没有涉及继承和派生时,private和protected是同等级的,外部不允许访问
struct和class的区别?
class默认访问权限为private,struct默认访问权限为public。
1.3将成员变量设置为private
- 1.可赋予客户端访问数据的一致性(不需要考虑访问的成员是否需要加括号)
- 2.可细微划分访问权限控制
2、面向对象程序涉及案例
2.1设计立方体类
class Cub{
private:
int L;
int W;
int H;
public:
void setL(int l){L = l;}
void setW(int w){W = w;}
void setH(int h){H = h;}
int getL(){return L;}
int getW(){return W;}
int getH(){return H;}
//计算立方体的面积
//计算立方体的体积
}
2.2点和圆的关系
3、对象的构造和析构
3.1初始化和清理
当我们创建对象时,这个对象应该由一个初始状态,当对象销毁之前应该销毁自己创建的一些数据。构造函数和析构函数,会被编译器自动调用,完成对象初始化和对象清理工作。
3.2构造函数和析构函数
构造函数主要作用在于创建对象时为对象成员的属性赋值,构造函数由编译器自动调用,无需手动调用。
析构函数主要用于对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:
1.构造函数函数名和类名相同,没有返回值,不能有void,但可以有参数;
2.classname(){}
析构函数语法:
1.析构函数函数名是在类名前面加~,没有返回值,不能有void,不能有参数,不能重载;
2.~classname(){}
3.3构造函数的分类及调用
构造函数和析构函数权限访问类型必须是public。
按参数类型:无参构造(默认构造)、有参构造
按类型:普通构造函数、拷贝构造函数(复制构造函数)
Person(const Person& p){ //引用传递一个Person对象,且不能被修改,完全复制。
cout << "拷贝构造函数!"<< endl;
m_age = p.m_age;
}
调用有参构造函数
- 括号法
Person p1(18); //定义一个对象p1,并给其年龄赋值为18
//拷贝构造函数也属于有参构造函数
Person p2(p1);//将对象p1的属性和操作全部复制给p2,Person p2 = p1
- 匿名对象(显式调用构造函数)
Person(18);
Person p3 = Person(18);
//匿名对象拷贝构造
person p4(Person(18));//等价于Person p4 = Person(18)
- =号法,隐式转换
Person p5 = 18; // Person p5 = Person(18);
//调用拷贝构造
Person p6 = p5; //等价于Person p6 = Person(p5);
Person p1 = Person(p2) 和 Person(p2)的区别?
当Person(p2)有变量(对象)来接的时候,那么编译器认为它是一个匿名对象;当没有变量来接的时候,编译器认为Person(p2)等价于Person p2。
注意:不能调用拷贝构造函数去初始化匿名对象。
Person p1;
Person(p1);//用拷贝构造的方式去初始化匿名对象了,等价于Person p1,重复定义。
3.4拷贝构造函数的调用时机
- 1.用一个旧对象初始化新对象
三种方式:括号法,等号法,匿名对象。 - 2.对象以值传递的方式传递给函数参数
传递的参数是普通对象,函数参数也是普通对象,传递将会调用拷贝构造。 - 函数局部对象以值传递的方式从函数返回
函数返回局部对象。return p;
3.5构造函数调用规则
默认情况下,C++编译器至少为我们写的类增加3个函数
1.默认构造函数(无参构造函数,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对类中非静态成员属性简单值拷贝
如果用户定义拷贝构造函数,编译器将不会再提供任何默认构造函数。
如果用户定义了普通构造函数(非拷贝构造函数),编译器不再提供默认无参构造,但会提供默认拷贝构造函数。
3.6深拷贝和浅拷贝
3.6.1浅拷贝
C++的默认拷贝构造函数就是浅拷贝,是对象数据成员的简单复制,但是两个对象共用同一个地址。一般情况下,浅拷贝没有任何副作用,但是当类中有指针,并且指针指向动态分配的内存空间时,当对象快结束时由于会两次调用析构函数,从而导致第二次析构时内存释放失败。
3.6.2深拷贝
需要自定义拷贝构造函数,自行给指针动态分配内存空间,仅仅把一个对象的数据复制过来,但是二者指向堆区不同的内存空间,两次析构时释放的是两个不同的内存空间。
3.7多个对象构造和析构
3.7.1初始化列表
//传统方式初始化
Person(int a, int b, int c){
m_a = a;
m_b = b;
m_c = c;
}
//初始化列表初始化
Person(int a, int b, int c):m_a(a),m_b(b),m_c(c){}
注意:初始化成员列表(参数列表)只能在构造函数使用
3.7.2类对象作为成员
当调用构造函数时,首先按照各对象成员在类中定义的顺序依次调用它们的构造函数,对这些对象依次初始化,然后再调用本身的函数体。也就是说,先调用对象成员的构造函数,再调用本身的构造函数。
析构函数和构造函数调用顺序相反,先构造的,后析构。
3.8 explicit关键字
声明为explicit的构造函数不能在隐式转换中使用,即不能将构造函数的参数列表内容直接赋值给对象。
explicit用于修饰构造函数,方式隐式转换;
是针对单参数的构造函数而言。(或者除了第一个参数外其余参数都有默认值的多参构造)
3.9动态对象创建
C语言提供了动态内存分配,函数malloc和free可以在运行时从堆中分配存储单元。
3.9.1对象创建
当创建一个C++对象时会发生两件事情:
1.为对象分配内存
2.调用构造函数来初始化那块内存
使用未初始化的对象是程序出错的一个重要原因。
3.9.2 C语言动态分配内存方法
- 程序员必须确定对象的长度;
- malloc返回一个void*的指针, C++不允许将void*赋值给其他任何指针,必须强转;
- malloc可能申请内存失败,所以必须判断返回值来确保内存分配成功;
- 用户在使用对象之前必须对它初始化,构造函数不能显式调用初始化,用户有可能忘记调用初始化函数。
3.9.3 new 运算符
在C++中,把创建一个对象所需要的操作都结合在一个称为new的运算符里。当用new创建对象时,它就在堆里为对象分配内存并调用构造函数完成初始化。
new运算符能确定在调用构造函数初始化之前内存分配时成功的,不需要显式确定是否调用成功。
3.9.4 delete 运算符
delete只适用于由new创建的对象。delete表达式先调用析构函数,然后释放内存,正如new表达式返回一个指向对象的指针一样,delete需要一个对象的地址。
3.9.5 用于数组的new和delete
使用new在堆上创建数组非常容易,如int* arr1 = new int[10]。释放数组内存为delete [ ] arr1.[ ]不能省略。
当创建一个对象数组的时候,必须对数组中的每一个对象调用构造函数。除了在栈上可以聚合初始化,创建堆上对象数组必须提供构造函数。
3.9.6 delete void*可能会出错
3.9.7 使用new 和delete采用相同形式
当我们使用delete的时候,必须让delete知道指针指向的内存空间中是否存在一个“数组大小记录”的办法就是告诉它。当用delete [ ] 时,delete就知道是一个对象数组,从而清楚该调用几次析构函数。
如果在new表达式中使用[ ],必须在相应的delete表达式中也使用[ ];如果new表达式没有使用,也一定不要在相应的delete表达式中使用。
3.10 静态成员
类中的成员如果用关键字static修饰,则为静态成员。
不管这个类创建了多少个对象,静态成员只有一个拷贝,这个拷贝被所有属于这个类的对象共享。
3.10.1静态成员变量
静态成员变量,在编译阶段就分配空间,对象还没创建时,就已经分配空间。
静态成员变量必须在类中声明,在类外定义。
静态数据成员不属于某个对象,在为对象分配空间中不包括静态成员所占空间。
静态数据成员可以通过类名或者对象名来引用。
静态成员也有访问权限,类外不能访问私有成员。
3.10.2 静态成员函数
在成员函数前加static成为静态成员函数。和静态成员一样,在对象还没有创建前,可以通过类名调用。静态成员函数主要是为了访问静态变量,但不能访问普通成员变量。
- 静态成员函数只能访问静态成员变量,不能访问普通成员变量;
- 静态成员函数的使用和静态成员变量一样;
- 静态成员函数也有访问权限;
- 普通成员函数可以访问静态成员变量,也可以访问普通成员变量。
3.10.3 const静态成员属性
如果一个类的成员既要实现共享,又要实现不可改变,则用static const 修饰。定义静态const数据成员时,最好在类内部初始化。
3.10.4 静态成员实现单例模式
单例模式是一种常见的设计模式。
它的核心结构中只包含一个被称为单例的特殊类。
通过单例模式可以保证系统一个类只有一个实例而且该实例易于外界访问。
如果在系统中某个类的对象只能存在一个,单例模式就是最好的解决方案。