接着上篇的内容,我们继续学习一些入门的知识。
本章学习的内容:函数重载、引用 、内联函数 、auto关键字、基于范围的for循环、指针空值nullptr
目录
上篇看这里:http://t.csdnimg.cn/62zG6
一、函数重载
在C语言中,是不可以出现同名函数的
但是在C++中函数重载则可以出现同名函数。
函数重载就是让函数名相同的函数能够实现多种类似的功能。这些同名的函数的形参不同(体现在形参的个数不同,类型不同,顺序不同)。
1.1 形参列表不同
接下来我们来看函数重载下的ADD函数:
形参个数不同:
//形参个数不同 int ADD(int a, int b) { cout << "ADD(int a,int b)" << endl; return a + b; } int ADD(int a) { cout << "ADD(int a)" << endl; return a; }
执行结果:
形参类型不同:
int ADD(int a, int b) { cout << "ADD(int a,int b)" << endl; return a + b; } double ADD(double a, double b) { cout << "ADD(double a, double b)" << endl; return a + b; }
执行结果:
形参类型顺序不同:
void Show(int a, double b) { cout << "int-double" << endl; } void Show(double a, int b) { cout << "double-int" << endl; }
执行结果:
1.2 返回值不同,是否可以构成重载?
答案:不构成
C++为什么支持重载,是因为有函数名修饰
简单来说,在函数名进入符号表时,经过了一遍修饰,即将形参信息也加入了函数名中。
红色框内就是函数名经过修饰后的名字,可能有些看不明白是如何修饰的,但是我们发现其中的HN和NH是换了位置,也就是对应刚才的形参顺序不同
证明了函数名修饰确实是把形参信息也加入了函数名中,这才能够构成重载。
那么我们再把返回值也加入函数名修饰规则不就行了?
我们来看下面的例子:只有返回值不同,是否构成重载
int Show(int a, double b) { cout << "int" << endl; return a; } double Show(int a, double b) { cout << "double" << endl; return b; }
因为在这种情况下,就算返回值也加入了函数名修饰中,我们也无法确定应该调用哪一个Show。
当然,也会报错:
二、引用
引用:引用就是取别名,给已存在的变量重新取一个名字,他们共用一块空间。
用法是:类型名& 别名 = 已存在的变量
void testRef() { int a = 1; int& b = a; cout << a << endl << b << endl; cout << &a << endl; cout << &b << endl; }
运行结果:
2.1 引用的特性
1. 引用在定义时必须初始化
2. 一个变量可以有多个引用
3. 引用只可引用一个实体,不可改变
下面代码演示:
void testFea() { int a = 1; int b = 2; //int& c; 1.在定义时必须初始化 int& c = a; int& d = a; //2.可以有多个引用 c = b; //3.这句不是改变引用,而是将b的值赋值给c cout << &a << endl << &b << endl << &c << endl << &d << endl; }
2.2 使用场景
2.2.1 引用可以做参数
//对曾经写过的交换函数做改进 void Swap(int& a, int& b) { int c = a; a = b; b = c; }
2.2.2 做返回值
int& Count() { static int n = 0; n++; cout << "n: " << n << endl; return n; }
注意:
int& Count() { //如果去掉static int n = 0; n++; cout << "n: " << n << endl; return n; }
变量a的值变成了一个随机值,是因为 Count函数调用结束后,a引用的对象n就会销毁,最后变成了随机值。
所以:如果函数返回时,返回对象还不会销毁,则可以使用引用返回,否则必须使用传值返回。
2.3 传值和传引用的效率比较
我们知道传值并不是直接传递,而是先生成一份临时拷贝,再用这个临时拷贝去赋值给变量,效率很低。
而引用就是给变量取了一个别名,效率就会提升很多。
下面我们举两个例子,来看看是否是这样:
#include <time.h> struct A { int a[10000]; }; void TestFunc1(A a) {} void TestFunc2(A& a) {} void TestRefAndValue() { A a; //记录传值的时间 size_t begin1 = clock(); for (size_t i = 0; i < 10000; ++i) TestFunc1(a); size_t end1 = clock(); // 记录传引用的时间 size_t begin2 = clock(); for (size_t i = 0; i < 10000; ++i) TestFunc2(a); size_t end2 = clock(); // 分别计算两个函数运行结束后的时间 cout << "TestFunc1(A)-time:" << end1 - begin1 << endl; cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl; }
传引用效率 > 传值效率
void TestReturnByRefOrValue() { //记录传值的时间 size_t begin1 = clock(); for (size_t i = 0; i < 100000; ++i) TestFunc1(); size_t end1 = clock(); // 记录传引用的时间 size_t begin2 = clock(); for (size_t i = 0; i < 100000; ++i) TestFunc2(); size_t end2 = clock(); // 计算两个函数运算完成之后的时间 cout << "TestFunc1 time:" << end1 - begin1 << endl; cout << "TestFunc2 time:" << end2 - begin2 << endl; }
传引用效率 > 传值效率
2.4 引用和指针对比
引用在语法意义上就是一个别名(外号),没有独立的空间,和引用实体共用一块空间。
而在底层实现上,引用是按照指针方式来实现的。
有何不同呢?
引用和指针的不同点:
1. 引用在定义时必须初始化,指针可以是空,没有要求
2. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
3. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32 位平台下占4个字节)
4. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
5. 有多级指针,但是没有多级引用
6. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
三、内联函数
3.1 内联的概念
以 inline 修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。
inline int ADD(int a, int b) { return a + b; }
使用ADD这个函数时,不会建立栈帧,而是将这个函数直接在调用处展开,提高效率
3.2 注意
1.inline 对于编译器而言只是一个建议,是否展开还要看编译器的内部实现
对于递归,复制的函数,编译器会忽略inline特性
2.inline 不建议声明和定义分离,因为inline被展开,就不会有函数地址,在链接时就会出现找不到的情况。
//test.h #include<iostream> using namespace std; inline void test_inline(int i); //tes.cpp #include"test.h" void test_inline(int i) { cout << i << endl; } //main.cpp #include"test.h" int main() { test_inline(5); return 0; }
报错信息:
四、auto关键字
概念:从字面意思来看 auto 就是自动的意思,猜对了,auto关键字的功能就是“自动”
随着我们继续往后学习,我们会遇到这样的情况:
#include <string> #include <map> int main() { //auto的使用1 int a = 0; auto b = a; //auto的使用2 std::map<std::string, std::string> m; std::map<std::string, std::string>::iterator it = m.begin(); auto it2=it; return 0; }
使用1:对于第一种使用,简直是大材小用,也没有使用的必要
使用2:但是对于第二种,面对 “std::map<std::string, std::string>::iterator” 这么长的类型名,难以拼写,auto的作用就凸显出来了。
4.1 使用细节
1. auto不能直接作为函数的参数
void TestAuto(auto a) {}
2.auto不能直接用来声明数组
void TestAuto() { auto n[] = { 1,2,3,4,5,6 }; }
五、基于范围的for循环
如何遍历一个数组?
是不是这个样子:
void TestFor() { int array[] = { 1,2,3,4,5,6,7,8,9 }; //遍历输出每个数 for (int i = 0; i < sizeof(array) / sizeof(int); i++) { cout << array[i] << ' '; } }
在C++中,增加了一个新东西,范围for
for循环的括号组成: for(用于迭代的变量:迭代的范围)
注意:与普通的循环一样,可以使用continue和break
void TestFor() { int array[] = { 1,2,3,4,5,6,7,8,9 }; //让每个数++ for (int& i : array) { i++; } //遍历输出每个数 for (int i:array) { cout << i << " "; } }
六、指针空值nullptr
在C语言中我们经常会使用NULL, C++中我们为何不继续使用NULL呢?
NULL实际是一个宏,在(stddef.h)中可以看到下面的代码:
#ifndef NULL #ifdef __cplusplus #define NULL 0 #else #define NULL ((void *)0) #endif #endif
所以在C++中,NULL被定义为了0,在使用时,会遇到如下的麻烦:
void test(int) { cout << "test(int)" << endl; } void test(int*) { cout << "test(int*)" << endl; }
本意NULL想表示的是空指针(void*)类型,但却被定义为0和我们的目的相悖
C++中引入nullptr来解决这个问题
注意:
1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr关键字
2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0) 所占的字节数相同
3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr
七、结语
你的点赞和关注是作者前进的动力!
最后,作者主页有许多有趣的知识,欢迎大家关注作者,作者会持续更新有意思的代码,在有趣的玩意儿中成长!