C++入门知识梳理

1 篇文章 0 订阅
1 篇文章 0 订阅

一. 重载

1. 什么是重载、重写(覆盖)、重定义(隐藏)?

重载(overload)

重载的定义:在同一作用域中,同名函数的形式参数(参数个数、类型或者顺序)不同时,构成函数重载。例如:

class A
{
public:
	int 	func(int a);
	void 	func(int a, int b);
	void 	func(int a, int b, int c);    	
	int 	func(char* pstr, int a);
};

需要注意的是:

1.函数返回值类型与构成重载无任何关系
2.类的静态成员函数与普通成员函数可以形成重载
3.函数重载发生在同一作用域,如类成员函数之间的重载、全局函数之间的重载

重写/覆盖(override)

重写的定义:派生类中与基类同返回值类型、同名和同参数的虚函数重定义,构成虚函数覆盖,也叫虚函数重写。
需要注意的是,这里有一个特殊情况,即协变返回类型。定义是:如果虚函数返回指针或者引用时(不包括value语义),子类中重写的函数返回的指针或者引用是父类中被重写函数所返回指针或引用的子类型。看示例代码:

class Base
{
public:
    virtual A& show()
    {
        cout<<"In Base"<<endl;
        return *(new A);
    }
};

class Derived : public Base
{
public:
     //返回值协变,构成虚函数重写
     B& show()
     {
        cout<<"In Derived"<<endl;
        return *(new B);
    }
};

如果派生类中定义了一个与基类虚函数同名但是参数列表不同的非virtual函数,则此函数是一个普通成员函数,并形成对基类中同名虚函数的隐藏,而非虚函数覆盖。

重定义/ 隐藏(hiding)

隐藏定义:指不同作用域中定义的同名函数构成隐藏(不要求函数返回值和函数参数类型相同)。比如派生类成员函数隐藏与其同名的基类成员函数、类成员函数隐藏全局外部函数。例如:

void hidefunc(char* pstr)
{
	cout << "global function: " << pstr << endl;
}

class HideA
{
public:
	void hidefunc()
	{
		cout << "HideA function" << endl;
	}

	void usehidefunc()
	{
		//隐藏外部函数hidefunc,使用外部函数时要加作用域
		hidefunc();
		::hidefunc("lvlv");
	}
};

class HideB : public HideA
{
public:
	void hidefunc()
	{
		cout << "HideB function" << endl;
	}

	void usehidefunc()
	{
		//隐藏基类函数hidefunc,使用外部函数时要加作用域
		hidefunc();
		HideA::hidefunc();
	}
};

隐藏的实质是;在函数查找时,名字查找先于类型检查。如果派生类中成员和基类中的成员同名,就隐藏掉。编译器首先在相应作用域中查找函数,如果找到名字一样的则停止查找。

重载重写=覆盖重定义=隐藏
即函数重载或运算符重载,需要在同一 作用域里,函数名相同,参数类型或者参数个数不同,返回值可同可不同用于多态,函数名和参数都相同,仅仅只有内部实现不同用于继承,基类和派生类中有同名成员,派生类成员将屏蔽基类对同名成员的直接访问,即在基类和派生类中的同名成员

2. 为什么C++支持重载,C语言不支持?

c和c++都属于编译型语言,也就是说着两种语言都需要经过编译、链接两个步骤才能够执行。
c语言在编译器编译的时候,在库中的名字为:_function
而c++在编译器编译以后,在库中的名字是:_function_x
也就是说,c语言如果遇到重名函数,链接的时候就会报错
而c++会根据修饰规则进行选择,因为编译后的名字是不一样的。

比如两个函数声明:

1)void f(int x, int y)
2)void f()

c语言在编译后都是: _f ;而c++是: _f 和 _f_int_int

3. extern “C”是干嘛?

extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言(而不是C++)的方式进行编译。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。

这个功能十分有用处,因为在C++出现以前,很多代码都是C语言写的,而且很底层的库也是C语言写的,为了更好的支持原来的C代码和已经写好的C语言库,需要在C++中尽可能的支持C,而extern "C"就是其中的一个策略。这个功能主要用在下面的情况:

1)C++代码调用C语言代码

2)在C++的头文件中使用

3)在多个人协同开发时,可能有的人比较擅长C语言,而有的人擅长C++,这样的情况下也会有用到

二.引用

1. 指针和引用的区别?

当调用函数时,传递的参数有传值、传指针、传引用这三种形式。

传值是直接开辟了一个跟主函数实参一样的空间(地址不一样),里面存放了了跟实参一样大小的值,就相当于数值大小相同但是位置不同。你在这个调用函数里使用这个一样大小的值,完全不影响主函数实参的值。就好比主函数的空间就是一栋楼,里面的一个房间里放着一些东西(相当于实参变量值)。现在我调用了一个函数,就相当于我在另一栋楼的另一个房间里面,把刚才第一个放东西的房间里面存的东西完全复制过来,所以你操作现在这个房间里面的东西,完全不影响原来的房间的东西呀。

传指针就不一样了。指针就是地址,我们要去找一个房间里面的东西,那么你得先找到门牌号,才能对照着门牌号去找到房间,从而找到你想要的东西,这就是指针的使用原理。传指针就是把实参的地址传过去了,而不是像刚才传值一样,直接开辟一个新的空间去复制数值,而是开辟了一个新的空间把实参的地址复制了过去。主函数的空间就是一栋楼,里面的一个房间放着一些东西(相当于实参变量值),这个房间有个门牌号(也就是实参的地址)。现在调用函数,就好比我把他家的门牌号(实参的指针)给你,跟你说你按照这个门牌号去找这个房间,然后再去找里面的东西。这样一来,你根据门牌号找到了原来的房间,一旦修改房间里面的东西,就一定会产生改变。所以根据指针修改指向的变量时,如果调用函数进行了修改,主函数的变量也就被修改了。

传引用就是传指针的升级版。引用可以看成变量的别称,就好像Tony老师的本名就叫赵铁柱一样,名字不一样但是人就是那一个人。所以你传引用的时候,修改了调用函数里的传递参数值,主函数的对应变量也会随之改变。但是原理还是传递指针,也就是地址。传引用的时候实际上是拷贝了实参的地址,然后你在调用函数里的操作表面上看是对变量的直接赋值,实际上是通过找到地址再改变变量的,这是一种间接寻址。但是为啥不直接用指针找地址再操作呢?而是封装成引用的外表了,很大的原因是安全。因为直接指针操作,那你很可能改变了指针,然后就找不到原来的地址了。就好比,我现在要去找房间(调用了其他函数要去访问主函数的实参变量值),然后给了你一块门牌号(相当于指针,也就是地址),万一你一不小心掉沟里了,门牌号弄丢了(指针被错误的修改),那你就找不到原来的房子了呀,你要是还继续去找错误的房子,把别人家房子里面的东西改了,等下直接程序就出错了(走错家门很危险的…)。所以别人就是怕你乱改,直接就给你封装好了。引用其实还可以让代码更加简洁清晰,一目了然(因为就相当于同一个变量在操作的感觉)。C语言是没有传引用的,C++把它加上了。原因我觉得是更方便了,也更安全了。

2.引用做参数和引用做返回值

引用作为函数参数

(1)在函数内部会对此参数进行修改
都知道C++里提到函数就会提到形参和实参。如果函数的参数实质就是形参,不过这个形参的作用域只是在函数体内部,也就是说实参和形参是两个不同的东西,要想形参代替实参,肯定有一个值的传递。函数调用时,值的传递机制是通过“形参=实参”来对形参赋值达到传值目的,产生了一个实参的副本。即使函数内部有对参数的修改,也只是针对形参,也就是那个副本,实参不会有任何更改。函数一旦结束,形参生命也宣告终结,做出的修改一样没对任何变量产生影响。
(2)提高函数调用和运行效率

引用作为函数返回值

(1)以引用返回函数值,定义函数时需要在函数名前加&

(2)用引用返回一个函数值的最大好处是,在内存中不产生被返回值的副本。

引用作为返回值,必须遵守以下规则:

(1)不能返回局部变量的引用。
主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。
(2)不能返回函数内部new分配的内存的引用。
例如,对于返回函数内部new分配内存的引用,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。
(3)可以返回类成员的引用,但最好是const。
主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。
(4)引用与一些操作符的重载。
流操作符<<和>>,这两个操作符常常希望被连续使用,例如:cout << “hello” << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。
(5)在另外的一些操作符中,却千万不能返回引用:±*/ 四则运算符。
它们不能返回引用。主要原因是这四个操作符没有side effect,因此,它们必须构造一个对象作为返回值,可选的方案包括:返回一个对象、返回一个局部变量的引用,返回一个new分配的对象的引用、返回一 个静态对象引用。根据前面提到的引用作为返回值的三个规则,第2、3两个方案都被否决了。静态对象的引用又因为((a+b) == (c+d))会永远为true而导致错误。所以可选的只剩下返回一个对象了。

三.inline

1. inline函数的作用及特性

在C++中,为了解决一些频繁调用的小涵数大量消耗栈空间或者是叫栈内存的问题,特别的引入了inline修饰符,表示为内联涵数。

内联函数具有一般函数的特性,它与一般函数所不同之处公在于函数调用的处理。一般函数进行调用时,要将程序执行权转到被调用函数中,然后再返回到调用它的函数中;而内联函数在调用时,是将调用表达式用内联函数体来替换。在使用内联函数时,应注意如下几点:

a.在内联函数内不允许用循环语句和开关语句。
b.内联函数的定义必须出现在内联函数第一次被调用之前。

优缺点

内联函数的优点内联函数的缺点
可以避免调用函数的开销。当函数体比较小的时候,内联函数可以令目标代码更加高效。对于存取函数以及其他一些比较短的关键执行函数。1. 由于将对函数的每一个调用都以函数本体替换之。所以会增加目标代码的大小。造成代码膨胀。这将导致程序体积太大,不利于在内存不大的机器上运行
2. inline函数无法随着程序库的升级而升级。如果程序库中包含内联函数,一旦内联函数被改变,那么所有用到程序库的客户端程序都要重新编译。如果函数不是内联函数,一旦它有任何修改,客户端只需要重新连接就好,远比诚信编译的负担少的多。在头文件加入或修改内联函数时,使用该头文件的所有源文件都必须重新编译。
3. 很多调试器无法调试内联函数。很多建置环境仅仅只能 “在调试板程序中禁止发生inlining”

2. C++有哪些技术可以替代宏?

inline 函数简介

inline 函数由 inline 关键字定义,引入 inline 函数的主要原因是用它替代 C 中复杂易错不易维护的宏函数。
函数定义时,在返回类型前加上关键字 inline 即把函数指定为内联,函数申明时可加也可不加。但是建议函数申明的时候,也加上inline,这样能够达到"代码即注释"的作用。

const代替宏

C语言用#define,C++也可以用,它还可以用const来表示常量。
1.const是有类型的,而宏是没有的。它等于是给某个数字或者字符串用特定的名字来表示,就是起一个别名,这样会很方便。
2.在编译器进行编译的时候,宏是直接替换的,并不会检查错误,这个时候如果出现错误就不好排查了。并且宏没有类型安全检查。
3.有些集成化的而工具可以对常量进行调试,宏不可以调试。
4.在C++中尽量不使用宏,而是用const代替。
注意:在类中不能初始化const数据成员,只能在构造函数的初始化列表中初始化

enum代替宏

比起const,enum更像宏,因为宏不可以取地址,enum也不可以,但是对const修饰的变量取地址是合法的。
class A { private: enum {num = 100} ; }

3. 为什么要去替代/或者宏的缺点是什么?

  1. 由于是直接嵌入的,可能造成代码冗余;

  2. 嵌套定义过多可能会影响程序的可读性,而且很容易出错;

  3. 对带参的宏而言,由于是直接替换,并不会检查参数是否合法,存在安全隐患。
    预编译语句仅仅是简单的值代替,缺乏类型的检测机制。这样预处理语句就不能享受C++严格的类型检查的好处,从而可能成为引发一系列错误的隐患。

四.nullptr和NULL的区别

1. C程序中的NULL**

在C语言中,NULL通常被定义为:#define NULL ((void *)0)

所以说NULL实际上是一个空指针,如果在C语言中写入以下代码

int  *pi = NULL;
char *pc = NULL;

编译是没有问题的,因为在C语言中把空指针赋给int和char指针的时候,发生了隐式类型转换,把void指针转换成了相应类型的指针。

2. C++程序中的NULL**

但是问题来了,以上代码如果使用C++编译器来编译则是会出错的,因为C++是强类型语言,void*是不能隐式转换成其他类型的指针的,所以实际上编译器提供的头文件做了相应的处理:

#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif

可见,在C++中,NULL实际上是0.因为C++中不能把void*类型的指针隐式转换成其他类型的指针,所以为了结果空指针的表示问题,C++引入了0来表示空指针,这样就有了上述代码中的NULL宏定义。
但是实际上,用NULL代替0表示空指针在函数重载时会出现问题,这就是用NULL代替空指针在C++程序中的二义性。

3. C++中的nullptr**

为解决NULL代指空指针存在的二义性问题,在C++11版本(2011年发布)中特意引入了nullptr这一新的关键字来代指空指针。

总结:

NULL在C++中就是0,这是因为在C++中void* 类型是不允许隐式转换成其他类型的,所以之前C++中用0来代表空指针,但是在重载整形的情况下,会出现问题。所以,C++11加入了nullptr,可以保证在任何情况下都代表空指针,而不会出现上述的情况,因此,建议以后还是都用nullptr替代NULL,而NULL就当做0使用。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值