【C++之语法篇002】

C++知识语法篇

前言:
前篇内容对于C++有一个基本认识,这篇文章开始将学习C++与C语言优化后的语法知识部分。
/知识点汇总/

1、缺省参数

1.1、什么是缺省参数?

概念
缺省参数是用来声明或定义函数时为函数的参数指定一个缺省值。
在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。

#include <iostream>
void Func(int a = 0)//缺省参数(形参)
{
	std::cout << a << std::endl;
}
int main()
{
	Func();//0,没有传参时,使用参数的默认值
	Func(10);//10,传参数时,使用指定的实参
	return 0;
}

1.2、缺省参数分类

1.全缺省参数
2.半缺省参数
另外,比如有三个参数,却只传了一个或两个参数,称为半缺省参数。否则,三个参数全是缺省参数时,称为全缺省参数
值得注意的是,对于多参数的传参时。不能隔开/条约的传参,需从左向右的进行传参。

#include <iostream>
void Func(int a = 1, int b = 2, int c = 3)//缺省参数(形参)
{
	std::cout << a << std::endl;
	std::cout << b << std::endl;
	std::cout << c << std::endl << std::endl;//endl等价于'\n'
}
int main()
{
	Func();//1,2,3,没有传参时,使用参数的默认值
	Func(10,20);//10,20,传参数时,使用指定的实参
	Func(10, 20,30);
	//不能隔开/条约的传参,需从左向右
	//Func(, 10, 20);
	//Func(, , 20);
	//Func(10, , 20);
	return 0;
}

1.3、缺省参数的总结

1.缺省参数只能在声明给,而不是在定义时给。否则,编译就会报错,语法参数不匹配
2.所有带缺省的参数必须放在参数列表的右侧,即先定义所有的非缺省参数,再定义缺省参数
3.缺省参数在公共头文件包含的函数声明中指定,不要在函数的定义中指出(如果在函数的定义中指定缺省参数值,在公共头文件包含的函数声明中不能再次指定缺省参数值)
4.缺省参数并不一定是常量表达式,可以是任意表达式,甚至可以通过函数调用给出。如果缺省实参是任意表达式,则函数每次被调用时该表达式被重新求值。但是表达式必须有意义
5.C语言不支持,但C++兼容C
6.实参个数可以与形参不同,即,半缺省和全缺省参数

2、函数重载

前言:自然言语中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载了。

2.1、什么是函数重载?

概念
函数重载是一种特殊情况,C++允许在同一作用域中声明几个类似的同名函数,
但是注意这些同名函数的形参列表(参数个数,类型,顺序)必须不同,常用来处理实现功能类似数据类型不同的问题。

在C++中不仅函数可以重载,运算符也可以重载。
例如:运算符 << , >> 。既可以做移位运算符,也可以做输出,输入运算符。
注意:重载函数的参数个数,参数类型或参数顺序三者中必须有一个不同

2.2、C支持函数重载?

C语言中,不允许函数名相同,所以不支持
C++中,允许函数名相同,支持重载,但有一定的要求
要求:函数名相同时,需要参数类型不同,构成函数重载。
C++函数重载要求的参数不同,一般有三种形式
1.参数类型不同;
2.参数顺序不同(本质还是类型不同);
3.参数个数不同。

举个直观的例子:

#include <iostream>
using namespace std;
int Add(int x, int y)
{
	cout << "int Add(int x, int y)" << endl;
	return x + y;
}
double Add(double m, double n)
{
	cout << "double Add(double m, double n)" << endl;
	return m + n;
}
void Add(double a, int b)
{
	cout << "void Add(double a, int b)" << endl;
}
int main()
{
	Add(1, 2);
	Add(1.01, 2.02);
	Add(1.001, 2);
	return 0;
}

2.3、那么对于函数重载,函数名相同该如何处理?

对于这个问题,不妨回顾一下,底层的编译过程,方便理解。
预处理、编译、汇编、链接分析
以栈的声明和定义为例:stack.h stack.c main.c

执行过程功能指令生成的文件
预处理展开头文件、宏替换、条件编译、去掉注释-E stack.i main.imain.i
编译语法、词法分析,检查语法,生成汇编代码-S stack.s main.smain.s
汇编把汇编翻译成二进制机器语言,生成目标文件-c stack.o main.omain.o
链接匹配符号表,结合目标文件(动态/静态链接),生成可执行程序main.exe

其中链接过程的关键:符号表 --> 匹配: 函数名和函数地址

另外从函数的栈帧与销毁的角度分析,汇编中call 函数名、函数名地址(就是执行函数的入口地址,第一条语句的地址),类比数组,数组名就是首元素地址,本质就是执行对应的指令
补充
程序中当函数有定义了,才会有函数入口地址。说明只有声明时,是没有函数的地址。只有定义没有声明,即:就是有地址没有call也是不行的。
简述:声明是承诺,定义是兑现,相辅相成的关系

所以对于这个问题可知,C语言不支持重载,因为链接时,直接用函数名去找地址,有同名函数,区分不开。

2.4、那么C++是如何支持重载?

而且当函数的声明和定义分开在不同文件时,怎么分辩?不得不提出C++对于C语言进行的优化,函数名修饰规则
C++链接过程也会生成一个符号表,符号表里除了函数名,还存着函数的修饰名,修饰名记录保存着函数的地址。
在调用函数时,编译器会通过符号表里查看修饰名来得到函数的地址实现调用
本质就是把参数带入进行修饰,完成函数名修饰规则,实现符号表的查表,解决函数名相同的处理。
但是请注意,对于不同的编译器,底层的规范有所不同,但原理相同

为了方便理解,就以典型的Linux中的链接举例。
比如典型的linux环境下,是以_Znntt…(可这样理解:_Z是格式+n是函数名长度+n(函数名name)+tt…(1个/多个参数的类型type))格式的函数名修饰规则

int Add(int x, int y);
double Add(double m, double n);
void Add(double a, int b);
int A(int x, int y);
int main()
{
    //转汇编观察
	Add(1, 2);//call _Z3Addii
	Add(1.01, 2.02);//call _Z3Adddd
	Add(1.001, 2);//call _Z3Adddi
	A(1, 2);//call _Z1Aii
	return 0;
}

再了解一下同样的代码,比如Vs2019环境下,是以 ?n@@YAttt@Z(理解记忆:?格式+n(函数名name)+@@YA格式+ttt…(返回值类型+参数类型)+@Z格式)

int Add(int x, int y);
double Add(double m, double n);
void Add(double a, int b);
void A(int x, int y);

int main()
{
	Add(1, 2);//call   ?Add@@YAHHH@Z
	Add(1.01, 2.02);//call ? Add @@YA NNN @Z
	Add(1.001, 2);//call ? Add @@YA XNH @Z

	A(1, 2);//call ? A @@YA XHH @Z
	return 0;
}
char --- D
int ---- H
double --- N
void --- X
.....

在这里插入图片描述

3、引用

3.1、什么是引用?

概念
引用不是新定义一个变量,而是给已存在的变量起一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
所以改变引用(别名)的值,就可以改变原变量的值

格式:类型&引用变量名(C++称为对象名) = 引用实体

int a = 10;
int& b = a;
b = 20;  --->  a=b=20

举例

#include <iostream>
using namespace std;
int main()
{
	int a = 0;
	//引用,b就是a的别名
	int& b = a;
	cout << &b << endl;
	cout << &a << endl;
	b++;
	a++;
	int& c = a;
	int& d = c;
	d++;
	return 0;
}

3.2、引用可做参数

引用常见于作为参数使用。

#include <iostream>
using namespace std;
void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
// 引用做参数
void Swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}
int main()
{
	int x = 0;
	int y = 1;
	Swap(&x, &y);
	Swap(x, y);
	return 0;
}

3.3、引用的特性

1.引用必须初始化
2.引用无法改变指向,所以无法完全替换指针

#include <iostream>
using namespace std;
int main()
{
	int a = 0;
	//1.引用必须初始化
	//int& b;
	//b = c;

	//2.引用定义后,不能改变指向
	int& b = a;
	int c = 2;
	b = c;//这句是赋值,并不是改变指向

	//3.一个变量可以有多个引用,多个别名
	int& d = b;
	return 0;
}

3.4、引用和指针的关系

指针和引用的功能是类似的,存在重叠性的;
C++的引用,对指针使用比较复杂的场景下能更好的进行替换,让代码更间接易理解,但是不能完全替代掉指针。
引用不能完全替代指针的原因:引用定义后,不能改变指向。
比如典型的链表,指针可以指向下一个结点,上一个结点,而引用无法改变其指向

#include <iostream>
using namespace std;
struct Node
{
	struct Node* next;
	struct Node* prev;
	int val;
};
//一级指针
void Pushback(struct Node* phead, int x)
{
	phead = newnode;
}
//对比 
//二级指针
void Pushback(struct Node** phead, int x)
{
	*phead = newnode;
}
//引用
void Pushback(struct Node*& phead, int x)
{
	phead = newnode;
}
int main()
{
	struct Node* plist = NULL;
	return 0;
}
#include <iostream>
using namespace std;
typedef struct Node
{
	struct Node* next;
	struct Node* prev;
	int val;
}Node,*PNode;

//对比:二级指针和typedef+引用
void Pushback(Node** phead, int x)
{
	*phead = newnode;
}
void Pushback(PNode& phead, int x)
{
	phead = newnode;
}
int main()
{
	PNode plist = NULL;
	return 0;
}

3.5、常引用

const对引用变量的影响。

void text()
{
	const int a = 10;
	//int& ra = a;   //编译出错,a是常量
	const int& ra = a;
	//int& b = 10;  //编译出错,b是常量
	const int& b = 10;
	double d = 12.34;
	//int& rd = d;   //编译出错,类型不同
	const int& rd = d;
}

3.6、引用的作用

1.引用作为参数(a.输出型参数,b.作为传出参数,对象比较大时,可明显减少拷贝,提高效率)
2.引用做返回值(a.修改返回对象,b.减少拷贝,提高效率)

void Swap(int& a, int& b);
int* preorderTreversal(struct TreeNode* root, int* returnSize);
int* preorderTreversal(struct TreeNode* root, int& returnSize);

对象比较大时,可明显减少拷贝,提高效率
指针也可以,引用这种场景更据性价比

#include <iostream>
#include <time.h>
using namespace std;

struct A
{ 
	int a[100000]; 
};
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void main()
{
	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;
}

3.7、野引用

类似于局部变量,返回变量出了函数作用域其生命周期就结束了,那么就不能用引用返回变量数据。
那么什么时候使用可以用引用返回?
1.static修饰变量 --静态变量
2.全局变量
3.堆区的变量

#include <iostream>
using namespace std;
//int func()
//{
//	int a = 0;
//	return a;
//}
//返回a的别名,但是此时函数结束后栈区上会销毁,那么去访问a属于非法访问,形成野引用。
//非法访问导致随机值。
int& func()
{
	int a = 0;
	return a;
}
int& fun()
{
	int a = 0;
	return a;
}
int main()
{
	int ret = func();//开辟一个整型空间
	//语法概念上引用就是一个别名,没有独立空间
	//int& ret = func();//并且此时给a的别名起了一个别名。导致两次非法访问,野引用。
	cout << "ret = " << ret << endl;
	fun();
	cout << "ret = " << ret << endl;
	return 0;
}

语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。

纯C++写法:引出:1.结构体中可直接定义函数。2.引用作返回值

#include <iostream>
#include <assert.h>
using namespace std;
//C++ 结构体 == 类
struct SeqList
{
	//成员变量
	int* a;
	int size;
	int capacity;
	//成员函数
	void Init()
	{
		a = (int*)malloc(sizeof(int) * 4);
		size = 0;
		capacity = 0;
	}
	void PushBack(int x)
	{
		//判容量
		if (size == capacity)
		{
			int newcapacity = capacity == 0 ? 4 : capacity * 2;
			int* tmp = (int*)realloc(a, sizeof(int) * newcapacity);
			if (tmp == NULL)
			{
				perror("realloc fail");
				return;
			}
			a = tmp;
			capacity = newcapacity;
		}
		a[size++] = x;
		capacity--;
	}
	//引用别名,结合了SLModity和SLGet
	//由此可见:引用别名具备双重功能
	int& SLGet(int pos)
	{
		assert(pos >= 0);
		assert(pos < size);
		return a[pos];
	}
	//提前了解C++更优的写法;
	int& operator[](int pos)
	{
		assert(pos >= 0);
		assert(pos < size);
		return a[pos];
	}
};
int main()
{
	SeqList s;
	s.Init();
	s.PushBack(1);
	s.PushBack(2);
	s.PushBack(3);
	s.PushBack(4);
	for (int i = 0; i < s.size; i++)
	{
		//cout << "SLGet=" << s.SLGet(i) << endl;
		cout << "SLGet=" << s[i] << endl;
		//等价原型:
		//cout << "SLGet=" << s.operator[](i) << endl;
	}
	cout << endl;
	for (int i = 0; i < s.size; i++)
	{
		//if (s.SLGet(i) % 2 == 0)
		//{
		//	s.SLGet(i) *= 2;
		//}
		if (s[i] % 2 == 0)
		{
			s[i] *= 2;
		}
	}
	for (int i = 0; i < s.size; i++)
	{
		//cout << "SLGet=" << s.SLGet(i) << endl;
		cout << "SLGet=" << s[i] << endl;
		//等价原型:
		//cout << "SLGet=" << s.operator[](i) << endl;
	}
	cout << endl;
	free(s.a);
	return 0;
}

3.8、引用的总结

(1).引用和指针的区别:

1.在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
2.在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。

(2).引用和指针的不同点:

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

(3)引用的作用:

1.引用作为参数(a.输出型参数,b.作为传出参数,对象比较大时,可明显减少拷贝,提高效率)
2.引用做返回值(a.修改返回对象,b.减少拷贝,提高效率)

  • 29
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C 语言学习(基础语法篇)是一本面向零基础初学者的学习指南,主要介绍了C语言的基本语法和相关知识。这本书对初级程序员所必须掌握的知识和技术也进行了讲解,比如“单步调试”,“编码规范”,ANSI函数库,文件操作,以及标准模板库STL的使用。在学习过程中,需要在C程序中引用<ctime>头文件来使用日期和时间相关的函数和结构。相比于C语言,Java具有类似的“形式和感觉”,但更易于使用,并且采用了以对象为导向的编程方式。通过Java编写的应用程序可以在单独的电脑上运行,也可以分布在一个网络上。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [C++ 学习(基础语法篇)](https://blog.csdn.net/qq_44755885/article/details/123927930)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [C++学习指南(语法篇)代码+pdf(pdf可直接打印)](https://download.csdn.net/download/weixin_42099203/10991148)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [Java自学视频教程-JavaSE基础-Java基础语法-02、Java语言概述.mp4](https://download.csdn.net/download/weixin_54787054/88246038)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值