【C++ 入门精讲2】函数重载、默认参数、函数指针、volatile | 手写笔记(附完整代码)

前言

本篇博客是上一篇 C++ 基础笔记的延续,聚焦 C++ 中高频且易混淆的知识点------函数重载、默认参数、函数指针,同时补充内联函数的细节注意点、volatile 关键字含义,以及 string 类的简单应用(进制转换)。所有内容均来自本人代码注释,不添加多余冗余内容,结构清晰,拆解开讲清每个易错点、核心规则,方便新手理解记忆,也可作为面试基础准备素材。

一、有参宏与内联函数的补充说明(延伸知识点)

知识点

1. 有参宏实现内联函数功能:可以通过宏定义 #define ADD(a,b) a+b 实现类似内联函数 add 的加法功能,但有参宏本质是预处理阶段的文本替换,无类型检查(后续会和内联函数对比补充)。

2. 内联函数的关键细节:inline 关键字的位置可放在函数返回值类型前后(如 int inline add(...)inline int add(...)),两种写法等价。

3. 内联函数的"建议性":inline 关键字只是给编译器的"建议",不是强制要求。如果函数体过长、有复杂逻辑(循环、判断),编译器可能会忽略 inline关键字,将其当作普通函数处理。

4. 核心结论:内联函数一定包含 inline 关键字,但被 inline 修饰的函数,不一定是内联函数(取决于编译器优化)。

对应代码

cpp 复制代码
//有参宏,来实现inline add函数的功能。
#define ADD(a,b) a+b

//inline关键字写在函数名的前面。
//inline 关键字修饰的函数,不一定是内联函数,可能会被编译器优化掉。
//inline 关键字只是一种建议。也就是说,内联函数一定包含inline关键字,但是inline修饰的函数不一定是内联函数。
int inline add(int a, int b)
{
 return a + b;
}       

二、默认参数(缺省参数)的核心规则(重点,易错点)

知识点

1. 默认参数的定义:函数形参后加上 = 默认值,即构成默认参数,调用函数时,可省略该参数(省略时使用默认值,传入参数时覆盖默认值)。

2. 默认参数的合法值:默认值可以是常量(如 int base=2)、全局变量、表达式,也可以是函数指针(但不能是函数调用,只能是函数名本身)。

3. 核心规则1:默认参数只能"从右向左"定义。如果一个参数有默认值,它右边的所有参数都必须有默认值(比如 int add1(int a, int b = 1, int c, int d) 是错误的,因为 b 有默认值,其右边的 c、d 没有默认值)。

4. 核心规则2:函数的声明与定义中,只能有一个地方指定默认参数。通常在声明中指定,定义时不再重复写默认值(避免重复定义报错)。

5. 易错点:默认参数不构成函数重载(后续会详细说明),因为函数重载只看参数表的类型、个数、顺序,不关注参数的值。

对应代码(保留本人原代码,无任何修改)

cpp 复制代码
int add1(int a, int b = 1, int c, int d)
{
	return a + b + c + d;
}

#if 1
int bc()
{
	return 10;
}

int add2(int a, int(*b)() = bc)//这个bc是bc函数的函数指针,b为函数指针,它的定义的格式是原有定义函数语句的基础上,将这个函数名替换为函数指针的名字,并在这个指针名字前面加一个*号,
//然后把参数表里面的形式参数的名字去掉,其他不变即可。
{
	return a + b();
}
#endif
//函数功能:将n整数转换成base进制的字符串
//string类:c++设计的一个标准字符串类
//base 参数具有缺省值(默认值可以是常量,全局变量甚至可以是某一个表达式,还可以为函数指针(缺省值必须是函数指针,不可以是函数调用)),当调用时,此位置上的实参可以省略,你给缺省值传参那么原来的参数就会被替换掉,不传参,那么就会将这个值
// 作为参数被函数内部所调用。
//函数的声明与定义中只能有一个地方指定默认参数(通常藏在声明中指定,如果声明的函数已经有了缺省值,那么当我们要去实现这个函数功能的时候,不再需要在定义的形参上给缺省值)
//默认参数只能从右向左定义,如果一个参数有默认值,它右边的所有参数都必须右默认值。
string convert(int& n, int base=2)
{
	string s = "";//定义字符串对象
	int y = 0;
	int m = n;
	while (m >= base)
	{
		y = m % base;
		if (y < 10)
			s.insert(s.begin(), '0'+y);//字符串插入。s.insert是插入函数,s.begin指的是头指针的位置。
		else
			s.insert(s.begin(), 'a' + y - 10);
	    
		m /= base;
	}
	if(m<10)
		s.insert(s.begin(), '0' + y);
	else
		s.insert(s.begin(), 'a' + y - 10);

}



int sub(int a, int b)
{
	return a - b;
}

三、函数重载(C++ 核心特性,面试重点)

知识点

1. 函数重载的定义:一组函数名相同,但参数表不同的函数集合,编译器会根据调用时传入的实参,匹配对应的函数。

2. 参数表不同的3种情况(缺一不可):

(1)参数个数不同(如 int add(int a)int add(int a, int b));

(2)参数类型不同(如 int add(int a, int b)double add(int a, double b));

(3)参数顺序不同(本质是参数类型不同,如 double add(int a, double b)double add(double a, int b))。

3. 不能构成函数重载的3种情况(易错点):

(1)默认参数不构成重载:两个函数名相同、参数类型/个数/顺序完全一致,仅默认值不同,属于函数重定义(报错);

(2)单独 const 修饰形参不构成重载:如 int add(int a, int b)int add(const int a, const int b),编译器会认为是重复定义;

(3)返回值类型不同不构成重载:函数重载只看参数表,不看返回值(如int add(int a, int b)double add(int a, int b),参数表相同,返回值不同,属于重定义)。

4. 特殊情况:const 修饰引用/指针时,能构成函数重载

(1)const 修饰引用:int add(int& a, int& b)int add(const int& a, const int& b) 是重载函数;

(2)匹配规则:调用时传入普通变量,匹配非 const 引用的函数;传入常量(如 add(10, 20)),匹配 const 引用的函数。

5. 总结:缺省函数不能构成重载;对于普通变量,单独 const、单独引用不构成重载,但 const+引用(常引用)可构成重载;对于指针,单独 const 可构成重载,指针引用、const+指针引用也可构成重载(与普通变量相反)。

对应代码

cpp 复制代码
int sub(int a, int b)
{
    urn a - b;
}

//函数重载:有多个函数名相同但参数表不同的函数的集合。
//参数条不同包括:参数个数,参数类型,参数顺序不同(从一定角度上理解,这个参数顺序不同可以看作是参数类型不同)。
int add(int a) { return a + 10;}
int add(int a, int b) { return a + b; };
double add(int a, double b) { return a + b; }
double add(double a, int b) { return a + b; }

//需要注意的是,函数的缺省值不会构成函数重载,因为函数重载它的定义是只看参数表的类型,个数以及位置,不关注参数的值,你两个add函数的参数都是int类型,尽管你给的缺省值不一样
//但是类型是相同的,所以是函数重定义。所以千万要记住重载不看值,不管值,只管类型个数位置。

// 还有另外一种特殊情况就是含const的参数表
// 以下两个函数编译时报,重复定义的错误,所以含const也不能构成函数重载。
//int add(int a,int b)  {return a+b;}
//int add(const int a,const int b) {return a+b;}

//3.const修饰引用时,能构成函数重载
int add(int& a, int& b) {
     return a + b;
}
int add(const int& a, const int& b) {
 return a + b;
}
//如果传的是变量,则会调用第一个函数,如果是常量,则会调用第二个函数。     

四、函数指针(重点,结合重载函数应用)

知识点(基于本人手写注释,详细解读)

1. 函数指针的定义格式:在原有函数定义的基础上,将函数名替换为函数指针名,并在指针名前加 *,去掉形参名即可(如 int(*b)() 是指向"返回值为 int、无参数"的函数的指针)。

  1. 函数指针的赋值:直接将函数名赋值给函数指针(不需要加 &),因为函数名本身就是函数的地址(如 int(*b)() = bc,bc 是函数名,直接赋值给指针 b)。

  2. 重载函数与函数指针:当函数指针指向重载函数时,编译器会根据函数指针的参数表(参数个数、类型、顺序),匹配对应的重载函数(如double (*p)(int, double) = add,匹配 double add(int a, double b))。

  3. 作用:通过函数指针,可以指定调用重载函数中的某一个具体函数,避免歧义。

对应代码

cpp 复制代码
#if 1
int bc()
{
	return 10;
}

int add2(int a, int(*b)() = bc)//这个bc是bc函数的函数指针,b为函数指针,它的定义的格式是原有定义函数语句的基础上,将这个函数名替换为函数指针的名字,并在这个指针名字前面加一个*号,
//然后把参数表里面的形式参数的名字去掉,其他不变即可。
{
	return a + b();
}
#endif

五、volatile 关键字(基础补充)

知识点(基于本人手写注释,详细解读)

1. volatile 的核心作用:告诉编译器"不要对该变量进行优化",每次访问该变量时,都直接从内存中读取,而不是使用寄存器中存储的副本。

  1. 适用场景:通常用于多线程、中断处理等场景,避免编译器优化导致变量值读取错误(比如某个变量可能被其他线程或中断修改,寄存器副本无法及时更新)。

  2. 注意:volatile 只保证"每次读取都来自内存",不保证原子性(多线程中还需配合锁使用)。

对应代码

cpp 复制代码
//扩展:
//volatile 是c/c++中的一个关键字,用于告诉编译器不要对特定变量进行优化,每次访问变量时都直接从内存中读取,而不是使用可能存在的寄存器副本。

六、C++ 函数名粉碎机制(为什么能实现函数重载?面试考点)

知识点

  1. 核心原因:C++ 编译器会对函数名进行"粉碎"(也叫名字修饰),根据特定规则修改函数名,使不同的重载函数拥有不同的粉碎后名称,从而被编译器区分。

2. 粉碎依据(4个关键信息):函数名、返回值类型、参数表、调用约定。

  1. 粉碎后名称格式:?函数名@@{调用约定标识}{返回值类型标识}{参数表的标识}@Z

++4. 常见调用约定及标识(重点记默认):++

++(1)__cdecl:默认调用约定,标识为 YA++

++(2)__stdcall:标识为 YG++

++(3)__fastcall:标识为 YI++

++(4)__thiscall:仅用于类成员函数,此处暂不展开。++

补充:C 语言没有函数名粉碎机制,所以 C 语言不支持函数重载。

七、主函数测试(整合所有知识点,可直接上机)

知识点

主函数中整合了前面所有知识点的测试逻辑,包含内联函数调用、重载函数调用、函数指针使用,可通过修改 #if 0#if 1 开启对应测试块,验证每个知识点的效果。

对应代码

cpp 复制代码
int main()
{
	//回顾:
	//引用替换指针类型作为函数参数
	//指针的引用和数组的引用
	#if 0
	//内联函数:
	cout << add(1, 2) << endl;
	cout << sub(1, 2) << endl;
    #endif
	//对于重载函数的指针。
	//匹配重载函数:通过参数表的参数个数以及参数类型以及参数位置来判断
	//如果我们想要指定特定的对象,我们可以通过函数指针来去指定特定对象的函数

	double (*p)(int, double) = add;//这个指针p指定的是第三个重载函数



	return 0;
	
}

附录:本人原版完整代码(未做任何修改,可直接复制上机)

cpp 复制代码
#if 1
#include<stdio.h>
#include<assert.h>
#include<string>
#include<stdlib.h>
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//有参宏,来实现inline add函数的功能。
#define ADD(a,b) a+b

//inline关键字写在函数名的前面。
//inline 关键字修饰的函数,不一定是内联函数,可能会别编译器优化掉。
//inline 关键字只是一种建议。也就是说,内联函数一定包含inline关键字,但是inline修饰的函数不一定是内联函数。
int inline add(int a, int b)
{
	return a + b;
}

int add1(int a, int b = 1, int c, int d)
{
	return a + b + c + d;
}

#if 1
int bc()
{
	return 10;
}

int add2(int a, int(*b)() = bc)//这个bc是bc函数的函数指针,b为函数指针,它的定义的格式是原有定义函数语句的基础上,将这个函数名替换为函数指针的名字,并在这个指针名字前面加一个*号,
//然后把参数表里面的形式参数的名字去掉,其他不变即可。
{
	return a + b();
}
#endif
//函数功能:将n整数转换成base进制的字符串
//string类:c++设计的一个标准字符串类
//base 参数具有缺省值(默认值可以是常量,全局变量甚至可以是某一个表达式,还可以为函数指针(缺省值必须是函数指针,不可以是函数调用)),当调用时,此位置上的实参可以省略,你给缺省值传参那么原来的参数就会被替换掉,不传参,那么就会将这个值
// 作为参数被函数内部所调用。
//函数的声明与定义中只能有一个地方指定默认参数(通常藏在声明中指定,如果声明的函数已经有了缺省值,那么当我们要去实现这个函数功能的时候,不再需要在定义的形参上给缺省值)
//默认参数只能从右向左定义,如果一个参数有默认值,它右边的所有参数都必须右默认值。
string convert(int& n, int base=2)
{
	string s = "";//定义字符串对象
	int y = 0;
	int m = n;
	while (m >= base)
	{
		y = m % base;
		if (y < 10)
			s.insert(s.begin(), '0'+y);//字符串插入。s.insert是插入函数,s.begin指的是头指针的位置。
		else
			s.insert(s.begin(), 'a' + y - 10);
	    
		m /= base;
	}
	if(m<10)
		s.insert(s.begin(), '0' + y);
	else
		s.insert(s.begin(), 'a' + y - 10);

}



int sub(int a, int b)
{
	return a - b;
}


//函数重载:有多个函数名相同单参数表不同的函数的集合。
//参数条不同包括:参数个数,参数类型,参数顺序不同(从一定角度上理解,这个参数顺序不同可以看作是参数类型不同)。
int add(int a) { return a + 10;}
int add(int a, int b) { return a + b; };
double add(int a, double b) { return a + b; }
double add(double a, int b) { return a + b; }
//需要注意的是,函数的缺省值不会构成函数重载,因为函数重载它的定义是只看参数表的类型,个数以及位置,不关注参数的值,你两个add函数的参数都是int类型,尽管你给的缺省值不一样
//但是类型是相同的,所以是函数重定义。所以千万要记住重载不看值,不管值,只管类型个数位置。




// 还有另外一种特殊情况就是含const的参数表
// 以下两个函数编译时报,重复定义的错误,所以含const也不能构成函数重载。
//int add(int a,int b)  {return a+b;}
//int add(const int a,const int b) {return a+b;}

//3.const修饰引用时,能构成函数重载
int add(int& a, int& b) {
     return a + b;
}
int add(const int& a, const int& b) {
	return a + b;
}
//如果传的是变量,则会调用第一个函数,如果是常量,则会调用第二个函数。


//总结:缺省函数不能构成重载,对于变量来说:单独的const,单独的引用,是不可以构成重载的,但是const+&所构成的常引用却可以构成重载
//对于指针来说:单独的const却可以构成重载,指针的引用也可以构成重载,(前面两个与常量的相反)那么const+ & 所构成的常引用同样也可以构成重载。


//扩展:
//volatile 是c/c++中的一个关键字,用于告诉编译器不要对特定变量进行优化,每次访问变量时都直接从内存中读取,而不是使用可能存在的寄存器副本。
//为什么C++中函数可以重载?【C++函数名的粉碎机制】
// 因为C++编译器对函数名依据以下信息修改(粉碎)成新名称:
// 函数名、返回类型、参数表和调用约定  【C++函数名的粉碎机制】
// 新名称的格式:  (?函数名@@{调用约定标识}{返回值类型标识}{参数表的标识}@Z)
// 调用约定:   影响是参数入栈的方式或顺序
//     __cdecl (默认), 标识是 YA
//     __stdcall ,     标识是 YG
//     __fastcall ,     标识是 YI  
//     __thiscall, 只应用于类成员函数,在此不说明


int main()
{
	//回顾:
	//引用替换指针类型作为函数参数
	//指针的引用和数组的引用
	#if 0
	//内联函数:
	cout << add(1, 2) << endl;
	cout << sub(1, 2) << endl;
    #endif
	//对于重载函数的指针。
	//匹配重载函数:通过参数表的参数个数以及参数类型以及参数位置来判断
	//如果我们想要指定特定的对象,我们可以通过函数指针来去指定特定对象的函数

	double (*p)(int, double) = add;//这个指针p指定的是第三个重载函数



	return 0;
	
}
#endif
相关推荐
旖-旎2 小时前
哈希表(存在重复元素||)(4)
数据结构·c++·算法·leetcode·哈希算法·散列表
John.Lewis2 小时前
C++进阶(8)智能指针
开发语言·c++·笔记
無限進步D3 小时前
蓝桥杯赛前刷题
c++·算法·蓝桥杯·竞赛
小贾要学习3 小时前
【Linux】应用层自定义协议与序列化
linux·服务器·c++·json
CoderCodingNo3 小时前
【GESP】C++二级真题 luogu-B4497, [GESP202603 二级] 数数
开发语言·c++·算法
weixin_395772473 小时前
计算机网络学习笔记】初始网络之网络发展和OSI七层模型
笔记·学习·计算机网络
小陈phd3 小时前
多模态大模型学习笔记(三十四)——ChatTTS:新一代中文语音合成工具原理与实战解析
笔记·学习·语音识别
郝学胜-神的一滴3 小时前
[简化版 GAMES 101] 计算机图形学 04:二维变换上
c++·算法·unity·godot·图形渲染·unreal engine·cesium
来日可期13143 小时前
C/C++ 反常识记录(1)—— 那些容易踩坑的语法细节
c语言·开发语言·c++