【C++学习】入门篇--基础知识(下篇)

接着上篇的内容,我们继续学习一些入门的知识。

本章学习的内容:函数重载引用 内联函数 auto关键字基于范围的for循环指针空值nullptr

目录

一、函数重载

1.1 形参列表不同

1.2 返回值不同,是否可以构成重载?

二、引用 

2.1 引用的特性

 2.2 使用场景

2.2.1 引用可以做参数

2.2.2 做返回值 

 2.3 传值和传引用的效率比较

2.4 引用和指针对比

三、内联函数 

3.1 内联的概念

3.2 注意

 四、auto关键字

4.1 使用细节

五、基于范围的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++为什么支持重载,是因为有函数名修饰

简单来说,在函数名进入符号表时,经过了一遍修饰,即将形参信息也加入了函数名中。

红色框内就是函数名经过修饰后的名字,可能有些看不明白是如何修饰的,但是我们发现其中的HNNH是换了位置,也就是对应刚才的形参顺序不同

证明了函数名修饰确实是把形参信息也加入了函数名中,这才能够构成重载。

那么我们再把返回值也加入函数名修饰规则不就行了?

我们来看下面的例子:只有返回值不同,是否构成重载

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

七、结语

你的点赞和关注是作者前进的动力!

最后,作者主页有许多有趣的知识,欢迎大家关注作者,作者会持续更新有意思的代码,在有趣的玩意儿中成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值