类和对象
声明和定义的区别
●声明是告诉编译器变量或函数的类型和名字,不会为变量分配空间
●定义就是对这个变量或函数函数进行内存分配和初始化,需要分配空间
●同一个变量可以被声明多次,但是只能被定义一次
●定义=声明 + 初始化
面向对象的三大特征
●封装:把属性(成员变量)和操作(成员函数)结合为一个独立的整体。
●继承:子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
●多态:多态是同一个行为具有多个不同表现形式或形态的能力。
访问修饰符
类中每个成员都有自己的访问修饰符,可以是三种情况之—。
●public公共的,类内,子类,对象(所有位置)都有权限访问这个成员
●private 私有的,只有当前类内才有权限访问这个成员
●protected受保护的,类内和子类有权限访问这个成员。
●类的默认访问修饰符为private
构造函数
一、构造函数的基本用法
1、构造函数概念
一个类的对象被创建的时候,编译系统对象分配内存空间,并自动调用该构造函数,由构造函数完成成员的初始化工作。因此,构造函数的核心作用就是,初始化对象的数据成员
2、构造函数的特点
(1)名字与类名相同,可以有参数,但是不能有返回值(连void也不行)。
(2)构造函数是在实例化对象时自动执行的,不需要手动调用。
(3)作用是对对象进行初始化工作,如给成员变量赋值等。
(4)如果定义类时没有写构造函数,系统会生成一个默认的无参构造函数,默认构造函数没有参数,不做任何工作。
(5)如果定义了构造函数,系统不再生成默认的无参构造函数.
(6)对象生成时构造函数自动调用,对象一旦生成,不能在其上再次执行构造函数
一个类可以有多个构造函数,为重载关系。
3、构造函数的分类
按参数种类分为:无参构造函数、有参构造函数、有默认参构造函数
按类型分为:普通构造函数、拷贝构造函数(赋值构造函数)
。
构造函数语法
●格式:没有返回值 类名(){}
●函数名称与类名相同
●构造函数可以有参数,可以重载
●编译器在创建对象的时候会自动调用构造函数
默认构造函数
●C++默认构造函数是对类中的参数提供默认值的构造函数,一般情况下,是一个没有参数值的空函数,也可以提供一些默认值的构造函数,
●如果用户没有定义构造函数,那么编译器会给类提供一个默认的构造函数,只要用户自定义了意一个构造函数,那么编译器就不会提供默认的构造函数,这种情况下,容易编译报错,所以正确的写法就是用户在定构造函数的时候,也需要添加一个默认的构造函数,这样就不会造成编译报错。
初始化参数列表
●只能在构造函数里使用该语法,可以给所有成员设置初始化参数。
●const类型和引用类型必须在初始化参数列表中初始化。
●成员的构造顺序和在初始化参数列表中的顺序无关,与在类中声明顺序有关
#include<iostream>
using namespace std;
class Box{
public:
int W;
int L;
int H;
Box(){} //机器自动构建的默认构造函数,它是缺省
Box(){ //无参构造函数
}
Box(int w,int l ,int h){//有参构造函数
this->W = w;
this->L = l;
this->H = h;
}
};
int main(){
return 0;
}
析构函数
●析构函数是作用于对象销毁工作,清空对象内部指针指向的的堆区内存。析构函数是在释放对象的时候自动调用,栈区对象自动释放。
析构函数语法:
●没有返回值:~类名(){}
●函数名称与类名相同但是前面要有~
●析构函数不可以有参数,因此不可以发生重载
●编译器在对象销毁前会自动调用析构函数,不需要手动调用
拷贝构造函数
静态成员
静态成员变量
特点:
静态成员变量属于整个类所有,所有
拷贝构造:
用已经存在的对象初始化新的对象,explicit 避免构造函数发生隐式转换。拷贝构造函数的参数使用引用避免递归。
浅拷贝:不同的对象里面的指针成员变量指向了同一块内存,其中任何一个对象修改或释放这块内存都 会影响到另外一个对象。
深拷贝:类中有成员变量为指针的时候用深拷贝构造
析构函数:释放对象中成员变量指向的堆区内存
构造函数不能作为虚函数,因为虚表指针在构造函数里赋值
子类不一定必须覆盖基类的纯虚函数(不覆盖子类仍是抽象类)和虚函数
纯虚类的虚表指针可以调用子类的虚函数
重载跟返回值无关
只要某个类可能包含虚函数,那么这个类将析构函数设为虚函数
子类可以隐式转换到父类,但父类不可隐式转换到子类
抽象类只能做其他类的基类,不能使用抽象类定义对象,
纯虚函数是其函数体为0,而不是返回值为 0,抽象类指针可以指派不同的派生类
空类的大小是1个字节,类所占内存的大小有成员变量(静态变量除外),虚表指针决定的
类中
#include<iostream>
using namespace std;
class A
{
public:
int *p = nullptr;
int n;
A(int n)
{
this->n = n;
if(n > 0)p = new int[n];
}
//拷贝构造为什么参数用万能引用在c++98中,因为既可以接收右值也可以接受左值,移动构造是在c11提出的
//浅拷贝︰两个对象里面的指针类型的成员变量指向相同的空间,其中一个对象修改或释放p指向的内存另一个对象随着变动
//A(const A&other) //万能引用
//{
//this->p = other.p;
//}
//深拷贝构造两个对象里面的指针类型的成员变量指向不同的空间
A(const A&other)
{
this->n = other.n;
//深拷贝构造两个对象里面的指针类型的成员变量指向不同的空间A(const A&other)
this->n = other.n;
this->p = new int[n];
for (int i = 0 ; i < n; i++)
{
p[i]= other.p[i];
}
}
//移动构造
A( A && other)
{
this->p = other. p;
other.p = nullptr ;
}
~A() //析构函数
{
if (p) delete[]p;
}
运算符重载(函数重载)
C++运算符操作对象只局限于基本的内置数据类型(int,float,double,char等),对于我们自定义的类型(类)是没有办法操作的。但是大多数时候我们需要对类进行类似的运算,这个时候我们就需要对运算符重新定义,赋予其新的功能,来满足要求。
C++重载运算符的实质是函数重载。运算符重载实质上是C++的一种多态,叫做静态多态,目的是为了让我们使用同名的函数来完成不同的操作。
#include<iostream>
using namespace std;
class Box{
public:
int length;
int width;
int height;
Box(){
length = 1;
width = 1;
height = 1;
}
//重载运算符需要关键字operator
Box& operator+(const Box& other){
Box box;
box.length = this->height + other.height;
box.width = this->width + other.width;
box.height = this.height + other.height;
return box;
}
void operator +=(const Box& other){
this->length +=other.length;
this->width +=other.width;
this->height += other.height;
}
void operator = (const Box & other){
this->height = other.height;
this->width = other.width;
this->length = other.length;
}
Box operator++(){
this->height += 1;
this->width += 1;
this->length +=1;
return *this;
}
Box operator++(int){
Box p = *this;
this->height += 1;
this->width += 1;
this->length += 1;
return p;
}
};
int main(){
Box a,b;
cout<<(a++)<<endl;
return 0;
}
继承
继承同名静态成员处理方式
●问题:继承中同名的静态成员在子类对象上如何进行访问?
o静态成员和非静态成员出现同名,处理方式一致
o访问子类同名成员直接访问即可
静态成员变量,子类和父类共用一个静态成员
非静态成员变量,各自使用各自的。
多态:
静态多态:编译期间就能决定的多态,例如函数重载和模板
动态多态(动态联编):父类的指针或引用指向子类对象,调用子类重写的虚函数
重写:
父类中的函数是虚函数,返回值函数名参数都相同,子类重写了父类的虚函数重写和隐藏:
只要函数名相同不是重写就是隐藏,函数隐藏到底调用哪个函数需要看调用该函数的指针引用或对象的类型,函数被隐藏后,子类对象可以通过作用域调用父类中的函数,但是父类对象不能通过作用域调用子类中的隐藏函数。多态的原理:
每个类只有一个虚表,所有对象共享同一个虚表,只要类中存有虚函数那么类就会有虚表只要有虚表那么类就会有虚表指针,每个对象都有各自的虚表指针。
当通过父类的指针或引用访问虚函数时,编译器会找到指针或引用指向的对象里面的虚表指针,通过虚表指针找到虚表调用相应的虚函数。
子类指针可以转换成父类指针(内存收缩)
父类指针不可以转换成子类指针(存在内存扩容可能占用其他内存,比较危险)
父类指针指向之类对象时,只能调用父类成员函数和子类重写的函数(若子类没有重写父类函数,则调用父类函数)
static
静态全局变量,静态局部变量他俩的作用范围不同,生命周期一致都是程序的生命周期,内存中没被初始化的存在bss段被初始化的存在data段,全局变量也会自动初始化为e,所以也存在bss段,静态全局变量或函数只能在当前文件中访问,在头文件中创建全局变量或函数时要使用静态全局变量。
静态成员变量:必须初始化并且要在类外初始化,属于类不属于对象,存在于静态区(全局区),使用sizeof 计算的时候不会计算静态成员变量的大小,在继承的时候静态成员变量不会被继承父类和子类共享静态成员变量﹐可以通过类名或对象名访问公有的静态成员变量。
如果比较器函数写在类内,则被定义为静态函数,由类调用
#include<iostream>
using namespace std;
class B
{
public:
B(int a){}
}
class A:public B
{
public:
//初始化参数列表,他是初始化不是赋值,所以变量初始化的顺序和成员变量生命的顺序一样和参数列表中的顺序无关
//常量和引用要在初始化参数列表中初始化,构造函数是给成员变量赋值的,引用和常量必须初始化
int a;
const int b;
int c;
A(int al, int a2, int a3) :c(a3),b(a2), a(al),B(a)//显示调用父类构造
{
A():B(1), b(1)//显示调用父类构造,因为父类没有无餐构造函数,创建子类对象必须创建父类对象,所以需要在子类的初始化参数列表中显示调用父类构造函数
{
工
int main()
{
A a;
}
习题