初遇C++之语法篇(完结)

博客主页:阿博历练记
文章专栏:c++
代码仓库:阿博编程日记
欢迎关注:欢迎友友们点赞收藏+关注哦

初遇C++之语法篇(完结)_第1张图片

文章目录

    • 1.函数重载
      • 1.1函数重载的概念
      • 1.2函数重载三大误区
      • 1.3C++支持函数重载的原理--名字修饰
    • 2.引用
      • 2.1引用概念
      • 2.2引用使用场景:
      • 2.3引用特性
      • 2.4常引用
      • 经典误区
      • 面试提问
    • 3.内联函数
      • 3.1内联函数的概念
      • 3.2内联函数的特性
    • 4.auto关键字
      • 4.1auto简介
      • 4.2auto不能推导的场景
    • 5.基于范围的for循环(C++11)
      • ❌误区
    • 6.指针控制nullptr(C++11)

1.函数重载

1.1函数重载的概念

函数重载:是函数的一种特殊情况,C语言不允许同名函数,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 类型类型顺序)不同友友们注意这里不比较返回值常用来处理实现功能类似数据类型不同的问题.

1.参数类型不同

#include
using namespace std;
int Add(int left, int right)
{
	cout << "int Add(int left, int right)" << endl;
	return left + right;
}
double Add(double left, double right)
{
	cout << "double Add(double left, double right)" << endl;
	return left + right;
}
int main()
{
	cout << Add(1, 2) << endl;
	cout << Add(1.1, 2.2) << endl;
	return 0;
}

初遇C++之语法篇(完结)_第2张图片

2.参数个数不同

#include
using namespace std;
void f()
{
	cout << "f()" << endl;
}
void f(int a)
{
 cout << "f(int a)" << endl;
}
int main()
{
	f();
	f(10);
	return 0;
}

初遇C++之语法篇(完结)_第3张图片
3.参数类型顺序不同

#include
using namespace std;
void f(int a, char b)
{
	cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
	cout << "f(char b, int a)" << endl;
}
int main()
{
	f(10, 'a');
	f('a', 10);
	return 0;
}

初遇C++之语法篇(完结)_第4张图片

1.2函数重载三大误区

❌误区一:

#include
using namespace std;
void f(int a, char b)
{
	cout << "f(int a,char b)" << endl;
}
void f(int b, char a)
{
	cout << "f(int b, char a)" << endl;
}
int main()
{
	f(10, 'a');
	f('a', 10);
	return 0;
}

在这里插入图片描述

友友们注意,函数重载是类型顺序不同,而不是形参的名字顺序不同.

❌误区二:

#include
using namespace std;
namespace  N1
{
	void func(int x)
	{

	}
}
namespace  N2
{
	void func(double x)
	{

	}
}
int main()
{
	return 0;
}

初遇C++之语法篇(完结)_第5张图片

#include
using namespace std;
namespace  N1
{
	void func(int x)
	{

	}
}
namespace  N2
{
	void func(int x)
	{

	}
}
int main()
{
	return 0;
}

初遇C++之语法篇(完结)_第6张图片

可能友友们会认为第①种构成函数重载,但是如果第一种构成重载的话,现在第②种很显然已经不是函数重载了,为什么程序还是可以编译通过呢,所以我们这里就可以从反面验证这里不构成函数重载,这里主要就是这两个函数不在同一个作用域里面,而函数重载要求在同一个作用域里面.

❌误区三:

#include
using namespace std;
void Func(int a)
{
	cout << "void Func(int a)" << endl;
}
void Func(int a,int b=10)
{
	cout << "void Func(int a,int b)" << endl;
}
int main()
{
	/*Func(1);
	Func(1, 2);*/
	return 0;
}

初遇C++之语法篇(完结)_第7张图片

友友们注意,这里构成函数重载,因为这两个函数参数的个数不同,与缺省参数没有关系.

#include
using namespace std;
void Func(int a)
{
	cout << "void Func(int a)" << endl;
}
void Func(int a,int b=10)
{
	cout << "void Func(int a,int b)" << endl;
}
int main()
{
	Func(1);
	Func(1, 2);
	return 0;
}

在这里插入图片描述

虽然它们构成重载函数,但是在调用的时候会存在歧义,比如当实参只有1时,这两个函数都可以调用,编译器就不知道该调用谁了,所以就会产生调用不明确的报错.

1.3C++支持函数重载的原理–名字修饰

初遇C++之语法篇(完结)_第8张图片
① C语言程序
初遇C++之语法篇(完结)_第9张图片
② C++程序

1.vs2022环境下
初遇C++之语法篇(完结)_第10张图片
2.Linux环境下
初遇C++之语法篇(完结)_第11张图片

2.引用

2.1引用概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间引用类型必须和引用实体是同种类型的.

2.2引用使用场景:

一.引用做参数

引用示例1:

1.传址交换

#include
using namespace std;
void swap(int*x1,int*x2)
{
	int tmp = *x1;
	*x1 = *x2;
	*x2 = tmp;
}
int main()
{
	int a = 3;
	int b = 5;
	swap(&a, &b);
	cout << a << endl;
	cout << b << endl;
	return 0;
}

2.引用交换

#include
using namespace std;
void swap(int&x1,int&x2)
{
	int tmp = x1;
	x1 = x2;
	x2 = tmp;
}
int main()
{
	int a = 3;
	int b = 5;
	swap(a, b);
	cout << a << endl;
	cout << b << endl;
	return 0;
}

所以友友们当我们使用引用之后,就可以不用指针了,因为x1就是a的别名,x2就是b的别名,所以它们两个就是a和b,我们只需要直接交换就可以了.

引用示例2:

1.二级指针

#include
#include
using namespace std;
typedef struct
{
	int val;
	struct ListNode* next;
}ListNode;
void PushBack(ListNode**pphead,int x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	newnode->val = x;
	newnode->next = NULL;
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//...
	}
}
int main()
{
	ListNode* plist = NULL;
	PushBack(&plist, 1);
	PushBack(&plist, 2);
	PushBack(&plist, 3);
	PushBack(&plist, 4);
	return 0;
}

友友们对于这种情况,在我们没有使用引用之前,我们必须使用传址的方式,用二级指针接收,然后解引用才能改变plist,如果用一级指针接收的话,那么形参就是实参的一份临时拷贝,形参的改变不会影响实参,所以友友们这种方法是不是比较麻烦.

2.引用交换

#include
#include
using namespace std;
typedef struct
{
	int val;
	struct ListNode* next;
}ListNode;
void PushBack(ListNode*&phead,int x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	newnode->val = x;
	newnode->next = NULL;
	if (phead == NULL)
	{
		phead = newnode;
	}
	else
	{
		//
	}
}
int main()
{
    ListNode* plist = NULL;
	PushBack(plist, 1);
	PushBack(plist, 2);
	PushBack(plist, 3);
	PushBack(plist, 4);
	return 0;
}

友友们这里phead就是plist的别名,所以它就是plist,所以改变phead就是改变plist,学完引用之后我们这里就可以不再用二级指针了.

二.引用做返回值

1.传值返回

#include
#include
using namespace std;
int count()
{
    int n = 0;
	n++;
	// ...
	return n;
}
int main()
{
	int ret = count();
	return 0;
}

2.传引用返回

#include
#include
using namespace std;
int& count()
{
    int n = 0;
	n++;
	// ...
	return n;
}
int main()
{
	int ret = count();
	//这里打印可能是1,也可能是随机值
	cout << ret << endl;
	return 0;
}

初遇C++之语法篇(完结)_第12张图片
代码验证

#include
#include
using namespace std;
int& count()
{
    int n = 0;
	n++;
	// ...
	return n;
}
int main()
{
	int &ret = count();
	//这里返回值可能是1,也可能是随机值
	cout << ret << endl;
	cout << ret << endl;
	return 0;
}

初遇C++之语法篇(完结)_第13张图片

知识小结:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。

2.3引用特性

1.引用必须在定义的时候初始化.
2.一个变量可以有多个引用,比如这里b,c,d都是a的引用.
初遇C++之语法篇(完结)_第14张图片
3.引用一旦引用一个实体,再不能引用其它实体.
初遇C++之语法篇(完结)_第15张图片

2.4常引用

#include
#include
using namespace std;
int main()
{
	const int a = 0;
	int x = 1;
	int& b = a;          //权限的放大
	const int& c = a;    //权限的平移
	const int& d = x;   //权限的缩小
	return 0;
}

友友们在引用过程中,权限可以平移缩小,但是不能放大.

经典误区

误区①

#include
#include
using namespace std;
int main()
{
	const int a = 0;
	int b = a;
	return 0;
}

这里b是a的赋值,把a的值拷贝给b,b的改变不会影响a,它和a根本不是一个变量,所以不涉及权限的放大.

误区②

#include
#include
using namespace std;
int main()
{
	int i = 0;
	double& d = i;
	return 0;
}

友友们这里注意,i是int类型,d是double类型,当i赋值给d时,这里会有隐式类型转换,这里会产生一个double类型的临时变量,临时变量具有常性不能修改,所以这里实质上是一种权限的放大,所以我们应该加一个const保持权限的平移才可以.

误区③

#include
#include
using namespace std;
int func()
{
    int a = 0;
	return a;
}
int main()
{
	int& ret = func();
	return 0;
}

友友们注意这里是传值返回,这里不是用变量a返回,这里会生成一个临时变量,用临时变量返回然后再拷贝给ret,临时变量具有常性,不能修改,所以我们这里在引用的时候实质上也是权限的放大,所以我们要加上const保持权限的平移就可以了.

面试提问

指针和引用的区别:
1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
2. 引用在定义时必须初始化,指针没有要求
3.引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
4.没有NULL引用,但有NULL指针
5.在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
6.引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
7.有多级指针,但是没有多级引用
8.访问实体方式不同,指针需要显式解引用,引用编译器自己处理
9. 引用比指针使用起来相对更安全

3.内联函数

3.1内联函数的概念

inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。

1.未用inline
初遇C++之语法篇(完结)_第16张图片
2.使用inline

查看方式

1. 在release模式下,查看编译器生成的汇编代码中是否存在call Add
2.在debug模式下,需要对编译器进行设置,否则不会展开(因为debug模式下,编译器默认不会对代码进行优化,以下给出vs2013的设置方式)

初遇C++之语法篇(完结)_第17张图片
初遇C++之语法篇(完结)_第18张图片

3.2内联函数的特性

1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用缺陷:可能会使目标文件变大,导致代码膨胀。优势:少了调用开销,提高程序运行效率。
初遇C++之语法篇(完结)_第19张图片
2 inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性.
3.inline不建议声明和定义分离,分离会导致链接错误。因为内联函数在调用的时候就展开了,所以编译器就没有生成函数的地址,所以在链接的时候就会找不到。

4.auto关键字

4.1auto简介

C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
注意:使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。

#include
#include
#include
#include
using namespace std;
int main()
{
	int a = 0;
	auto b = a;
	auto c = &a;                  //普通场景没有价值,类型很长时很有价值,可以简化我们的代码
	auto& d = a;
	std::vector<std::string> v;
	std::vector<std::string>::iterator it = v.begin();
	auto it = v.begin();
	return 0;
}

友友们,这里我们也可以通过typeid看它们的类型

int main()
{
	int a = 0;
	auto b = a;
	auto c = &a;                  //普通场景没有价值,类型很长时很有价值,可以简化我们的代码
	auto& d = a;
	std::vector<std::string> v;
	//std::vector::iterator it = v.begin();
	auto it = v.begin();
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(it).name() << endl;
	return 0;
}

初遇C++之语法篇(完结)_第20张图片

4.2auto不能推导的场景

1. auto不能作为函数的参数
void TestAuto(auto a)
{} ,此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导,比如我们实参传了一个3,编译器无法判断是整形3还是字符3.
2.auto不能直接用来声明数组
auto b[] = {4,5,6}; 这种情况也是不允许的,编译器也无法判断类型.

5.基于范围的for循环(C++11)

#include
#include
using namespace std;
int main()
{
	int array[] = { 1, 2, 3, 4, 5 };
	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
		array[i] *= 2;
	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
		cout << array[i] << " ";
	cout << endl;
	for (auto e: array)
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

初遇C++之语法篇(完结)_第21张图片

范围for循环会依次取数组中的数据赋值给e,自动判断结束,自动迭代.

初遇C++之语法篇(完结)_第22张图片

友友们,这里我们对x乘以2,为什么打印出来的数据还是没有变化呢,这里一定要注意,x是数组数据的拷贝,x的改变不会影响数组中的数据,所以我们需要加一个引用就可以了,此时x就是数组中数据的别名,就是数组中的数据,所以此时x的改变就会影响数组中的元素了.

❌误区

void TestFor(int array[])
{
    for(auto& e : array)
        cout<< e <<endl;
}

友友们,这种是不可以的,当使用范围for循环的时候,array那里必须是数组名,C++实际上只把形参数组名作为一个指针变量来处理,用来接收从 实参传过来的地址.

6.指针控制nullptr(C++11)

using namespace std;
void f(int)
{
	cout << "f(int)" << endl;
}
void f(int*)
{
	cout << "f(int*)" << endl;
}
int main()
{
	f(0);
	f(NULL);
	f((int*)NULL);
	return 0;
}

初遇C++之语法篇(完结)_第23张图片

友友们注意,在C++中,NULL的类型就不是指针类型了,我们如果要当成指针使用的话,需要强转类型
注意:
1.在C++中引入了一个关键字nullptr,在使用nullptr表示指针空值时,不需要包含头文件
2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
3.为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr

✨✨好了友友们,这期的内容到这里就告一段落了,下期不见不散.

你可能感兴趣的:(C++,c++,开发语言)