notescpp

const 的三种用法

(1)const int *p; const在类型前,称指向常量的指针,可以这样理解它的功能,因为const在int前,所以p指向的这个int变量对于 * p来说是const的,即不能通过* p改变这个变量的值,但是变量本身可以随便改变自己的值。另外也可以改变p的指向。

例:

int x=2;

int y=3;

const int *p=&x;

*p=4;   //错误,不能通过*p改变变量的值

x=4;    //可以,x不是一个常量,可以改变自己的值

*p=&y;   //可以,指针p本身不是一个常量,可以改变p的指向。


(2)int * const q; const在指针q的前面,叫常量指针,也就是说指针q本身是个常量,不能改变q的指向。但是可以通过*q改变所指向变量的值。

例:

int x=2;

int y=3;

int * const q=&x;     //注:因为指针q是常量,在声明时就应该初始化。

*q=4;   //可以

x=4;    //可以,x不是一个常量,可以改变自己的值

*q=&y;   //错误,指针q本身是一个常量,不能改变它的指向。

(3)const int * const pq; 这是前两种的结合,叫指向常量的常量指针。功能也是前两者的结合,即不能改变pq的指向,也不能通过*pq改变所以变量的值。

例:

int x=2;

int y=3;

const int * const pq=&x;

*pq=4;   //不可以,不能通过*pq改变所指变量的指。

x=4;    //可以,x不是一个常量,可以改变自己的值

*pq=&y;   //错误,指针pq本身是一个常量,不能改变它的指向。

总结:从上面可以看出,不论那种情况,变量本身都是可以改变自己的值的,除非变量本身就是声明成常量(const int x=2;)。区别三种情况关键就是看const在类型前还是在指针前,在类型前,不能通过*p改变变量的值,在指针前不能改变指针的指向,且在声明时就要初始化。

重载运算符

是对象之间的关系可以用成员函数实现

重载=运算符(重要)

<< ++ –

前提: 类 中 有 指 针 成 员 \color{#FF3030}{类中有指针成员} 必须重载=运算符默认浅拷贝就可以完成
如果没有指针成员就不需要重载赋值运算符

Person ob2=ob1;

调用的不是赋值运算 而是 拷 贝 函 数 , 旧 对 象 给 新 对 象 初 始 化 \color{#FF3030}{拷贝函数,旧对象给新对象初始化} 默认拷贝构造就是单纯的赋值

Person ob3;//无参构造被屏蔽必须手动实现
ob3=ob1;//这里是赋值,旧对象给新对象赋值,默认是浅拷贝

class Person
{
public:
	char *name;
	
	Person(char* name)
	{
		cout << "有参构造" << endl;
	}
	~Person()
	{
		cout << "析构函数" << endl;
	}

	Person(const Person& ob)
	{
		cout << "拷贝构造函数" << endl;
		this->name = new char[strlen(ob.name) + 1];
		
		strcpy(this->name, ob.name);
	}
	Person& operator =(Person& ob)
	{
		if (this->name != NULL)
		{
			delete[] this->name;
			this->name = NULL;//释放以前的空间
		}
		//申请空间
		this->name = new char[strlen(ob.name) + 1];
		//拷贝内容
		strcpy(this->name, ob.name);
		return *this;
	}

};
 

重载!= 和= 运算符

public:
	char* name;
bool operator==(Person & ob)
	{
	if (strcmp(this->name, ob.name) == 0)
	{
		return true;
	}
	else return false;

	}

函数调用符()的重载(了解)

int operator()(int x, int y)
	{

		return x + y;
	}//在类Fun中

Fun fun;
	fun.operator()(100, 200);
	//优化 fun和小括号结合
	cout << fun(100, 200) << endl;//输出结果为300

    //此处Fun 是类名称
	//Fun()是个匿名对象
	cout << Fun()(100, 200) << endl;

注意: 不是一个函数 仅仅是fun和()结合 调用了()重载运算符,因此把fun(100,200)叫做一个 仿 函 数 \color{#FF3030}{仿函数} 仿
仿函数的调用是对象名和()结合

不要重载&&、||(用户无法实现短路特性)

符号重载总结

= ->操作符只能通过成员函数进行重载 << >> 只能通过全局函数配合友元函数进行重载,因为他的左边不是对象 不要重载&& 因为无法实现短路规则

运算符建议使用
所有的一元运算符成员
= () [] -> ->*必须是成员
+= -= /= *= ^=&= != %= >>= <<=成员
其他二元运算符非成员

强化训练运算符 字符串类string

重载[]运算符 返回值必须是左值 返回值必须是引用
Mystring  str1"hello world";
cout<<str1[1];


char& Mystring::operator[](int index)

   {
       if (index >= 0 && index < this->size)
       {
           return this->str[index];

       }
       else
       {
           cout << "index无效 " << endl;
       }
   }


重载类型转化符
operator int()
   {
   	return (int)real;
   }
前面不可以有返回类型,数据转化函数,C++提供类型转换函数(type conversion function)来解决这个问题。类型转换函数的作用是将一个类的对象转换成另一类型的数据。

一般形式:
operator 类型名( )
   {
       实现转换的语句
   }

s t r c a t 用 于 拼 接 两 个 字 符 串 常 量 \color{#FF3030}{strcat 用于拼接两个字符串常量} strcat

重载+运算符 用于两个字符串常量拼接

 Mystring& operator+( const Mystring&ob)
    {
        int newsize = this->size + ob.size;

        char* tmp_str = new char[newsize];
        //清空tmp_str所指向的空间
        memset(tmp_str, 0, newsize);

        strcpy(tmp_str, this->str);
        strcat(tmp_str,ob.str);
        static Mystring newString(tmp_str);//局部对象不可以用引用???
        //释放tmp_str所指向的临时空间

        if (tmp_str != NULL)
        {
            delete[]tmp_str;
            tmp_str = NULL;
        }
        return newString;
    }
bool operator==(const char* str)
    {
        if (strcmp(this->str, str) == 0)
        {
            return true;
        }
        else return false;
    }

继承与派生

三个关键字的访问范围:
public:能被类成员函数,子类函数,友元访问,也能被类的对象访问
private:只能被类成员函数及友元访问,不能被其他任何访问,本身的类对象也不行
protected:只能被类成员函数,子类函数以及友元访问,不能被其他任何访问,本身的类对象也不行

基类就是父类,派生类就是子类

class NewsPage:publuc IndexPage
{

};

子类继承父类体现了共性,但子类又具有个性
单继承:一个父类派生一个子类
多继承:多个父亲
子类继承父类的全部成员,(构造和析构函数除外)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l2x6RIUU-1625473554485)(\Users\86156\Pictures\onlineclass.png)]

构造和析构函数的顺序
父类 对象成员 子类本身的构
子类对象成员 父类
子类默认调用父类的无参构造,想要调用有参构造,要显示调用,用初始化列表

对象默认是浅拷贝,深拷贝是无法继承的

菱形继承

多继承容易产生菱形继承,产生二义性
解决二义性可以增加作用域 还可以用虚基类

虚继承

继承的动作 虚继承
继承的基类 虚基类
class 子类:virtual public 父类

当子类虚继承父类时为什么会增加空间呢
增加了一个vbptr虚基表指针 虚继承时,虚基类指针vbptr指向虚基类表vbtable,虚基类表中存放的就是数据相对于虚基类指针的偏移,从而根据偏移找到数据
vbptr ==> vbtable
产生vbptr 和vbtable 的目的是不管有多少个继承 虚基类的数据只有一份
vbtable 保存了当前的虚指针和对于虚基类的首地址的偏移量

多态

c++支持编译时多态(静态多态)和运行时多态(动态多态),运算符重载和函数重载就是静态多态。而派生类和虚函数 实现运行时多态。
静态和动态的区别就是函数地址是早绑定(静态联编)还是晚绑定函数的入口/调用地址不能在编译期间确定,而是需要在运行时才能决定(动态联编)。

定义一个基类指针申请了 cat 保存了cat的起始地址
指针的类型是animal 所以说可以指向子类 但animal只能操作animal的那一部分 animal *p=new cat 向上转换

基类指针、引用指向子类对象 安全
子类指针、引用 指向基类对象
怎么用基类指针保存子类对象同时操作子类成员呢
使用虚函数

正常情况下,基类指针、引用只能访问子类对象中基类部分数据
只要涉及到继承 子类中同名函数 都是虚函数
##使用基类指针、引用访问子类对象中的成员方法(虚函数)
使用virtual修饰成员函数,该成员函数就是虚函数。
虚函数会产生一个虚指针vfptr,指向虚函数表vftabale,表中存放的是一个个虚函数的入口地址(普通成员函数不占类的空间) 虚函数的本质是一个函数指针变量
如果 没有涉及到继承,函数指针变量 就指向自身,这样就没有意义

拥有虚函数的类涉及到继承

当虚函数涉及到继承的时候,子类会继承父类的vbptr和vfptr编译器会将虚函数表中的函数入口地址 更新成子类的同名 (返回值、参数都相同)的函数入口地址。
如果基类指针、引用 访问虚函数的时候 就会间接的调用子类的虚函数

C++的动态捆绑机制是怎么样的?

首先 我们看看编译器如何处理虚函数,当编译器发现我们的类中有虚函数的时候,编译器会创建一张虚函数表,把虚函数的函数入口地址放到虚函数表中,并且增加一个指针vptr,这个指针是指向对象的虚函数表,在多态调用的时候,根据vptr指针,找到虚函数表来实现动态绑定。

Animal 类型的指针P 在没有涉及虚析构的时候 只能调用父类的析构函数,解决这个问题要用虚析构(虚函数)
虚析构作用:通过基类指针、引用释放子类所有空间
在析构函数前加virtual
作用:为了解决基类的指针指向派生类对象,并用基类的指针删除派生类对象。
子类析构调用完,自动调用父类析构

虚函数实现多态的条件

► ①类之间的继承关系满足赋值兼容性规则;
► ②改写了同名的虚函数;
► ③根据赋值兼容性规则使用指针(或引用)。

满足前两条并不一定产生动态联编,必须同时满足第3条才能保证实现动态联编.

在虚函数中使用成员名限定,可以强制解除动态联编

class A
{
public:
	virtual int get()
	{return 0;}
};
class B:public A
{
public:
	int get()
	{return 1;}
};
int main()
{
	B b;
	A *p=&b;  //子类对象赋给基类指针 ,由于父类是个虚函数,故动态联编,调用子类自身的同名函数
	cout<<p->get()<<endl;     //动态联编, 调用子类的同名函数
	cout<<p->A::get()<<endl;  //使用成员名限定可以强制解除动态联编, 调用父类的函数
	cout<<"-------------"<<endl;
	B *p1=&b; //子类对象赋给子类自身类型的指针
	cout<<p1->get()<<endl;
	return 0;
}
 
 
 
运行结果:
 
1
0
-------------
1
Press any key to continue
 

简单来说就是加上作用域标识符

纯虚函数 抽象类

virtual void sleep(void)=0;//不是真正的赋值0

如果一个类中有纯虚函数,那么这个类叫 抽 象 类 \color{#FF3030}{抽象类} 不可以实例化对象 但可以实例化对象指针 Animal *p
抽象类派生子类,子类必须实现所有的纯虚函数,否则子类也是抽象类
抽象类就是提供一个固定的流程和接口 ,不需要实现函数体

接口继承

多继承有争议,接口继承毫无争议,接口类只是一个功能声明,并不是功能实现,子类根据功能说明定义功能实现,注意:除了析构函数外,其他声明都是纯虚函数
###纯虚析构函数

class B
{//1、
	virtual ~B ()=0;
}
//2、
B::~B(){}//必须有析构函数的函数体
//原因:通过基类指针 释放了子类对象时,先调用了子类析构,再父类析构(如果父类的析构不实现,无法实现调用)

虚函数 纯虚函数 虚析构 纯虚析构

  1. 虚函数:只是virtual修饰,有函数体(作用于成员函数)
    目的:通过基类指针或引用操作子类的方法
class B
{
	public:
	virtual my_fun(void)
	{
     //有函数体
	}
};
  1. 纯虚函数:virtual 修饰加=0 没有函数体,所在的类为抽象类
    目的:提供固定流程和子类的接口
class B
{
	public:
	virtual my_fun(void)=0;

};
  1. 虚析构:virtual 修饰类中的析构函数
    目的:用基类指针删除派生类对象
class B
{
	public:
	virtual ~B()
	{
     //有函数体
	}
};
  1. 纯虚析构
    目的:同上删除派生对象
class B
{
	public:
	virtual ~B()=0;

};

B::~B()
{

}

重写 重载 重定义 (了解)

  1. 重载
    1.1 同一作用域,参数个数,参数顺序,参数类型不同
    1.2和函数返回值没有关系
    1.3const 也可以作为重载条件 归属为 参数类型不同
  2. 重定义
    1.1 首先要有继承
    1.2 子类重新定义父类的同名成员 (非virtual函数)
class Base 
{
	public:
	void fun(int ){}
	void fun (int ,int){}
}
class Son :public Base
{
	public:
	void fun (){}//同名,参数可以不同,返回值可以不同
}
  1. 重写(覆盖)
    1.1 有继承
    1.2 子类重写父类的virtual 函数
    1.3 函数返回值,函数名字,函数参数,必须和基类中的虚函数一致
class Base 
{
	public:
	virtual void fun(int){}
	
}
class Son :public Base
{
	public:
	virtual void fun (int){}//完全一致

函数模板

也是可以重载的
函数模板经过两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。

模板的局限性

如果T是数组,就不能进行赋值操作,进行比较时,比较的是地址之间的关系。如果T是结构体,就无法判断两个变量之间的大小关系。为了解决这种问题,可以提供模板的重载,为这些特定的类型提供具体化的模板。

C++类型转换

6.1 静态转换(static_cast)
6.2 动态转换(dynamic_cast)
6.3 常量转换(const_cast)
6.4 重新解释转换(reinterpret_cast)

静态转换(static cast)

al 函数
1.3 函数返回值,函数名字,函数参数,必须和基类中的虚函数一致

class Base 
{
	public:
	virtual void fun(int){}
	
}
class Son :public Base
{
	public:
	virtual void fun (int){}//完全一致

函数模板

也是可以重载的
函数模板经过两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。

模板的局限性

如果T是数组,就不能进行赋值操作,进行比较时,比较的是地址之间的关系。如果T是结构体,就无法判断两个变量之间的大小关系。为了解决这种问题,可以提供模板的重载,为这些特定的类型提供具体化的模板。

C++类型转换

6.1 静态转换(static_cast)
6.2 动态转换(dynamic_cast)
6.3 常量转换(const_cast)
6.4 重新解释转换(reinterpret_cast)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值