C++篇:C向C++迈进(下)

目录

引言

缺省参数

1.缺省参数的概念

2.缺省参数的分类

[2.1 全缺省](#2.1 全缺省)

[2.2 半缺省](#2.2 半缺省)

3.注意事项

函数重载

1.函数重载的定义

2.函数重载的基本规则

3.函数重载的运用场景

引用

1.引用的概念

2.引用的主要特性

3.常引用

4.引用的使用场景

[4.1 函数参数传递](#4.1 函数参数传递)

[4.2 函数返回值](#4.2 函数返回值)

5.引用和指针的差异

[5.1 定义和本质](#5.1 定义和本质)

[5.2 初始化和赋值](#5.2 初始化和赋值)

[5.3 空值](#5.3 空值)

[5.4 内存占用](#5.4 内存占用)

[5.5 安全性](#5.5 安全性)

内联函数

1.内联函数的定义

2.注意事项

nullptr空指针

结束语


引言

C++篇:C向C++迈进(上)中我们学习了一些有关C++的知识,接下来我们继续学习。

本文将介绍了C++中的缺省参数、全缺省和半缺省参数、函数重载、引用、内联函数,以及nullptr的使用。

求点赞收藏关注!!!

缺省参数

1.缺省参数的概念

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

举个例子:

void func(int a = 0)
{
	cout << a << endl;
}

int main()
{
	func();  // 没有传参时,使用参数的默认值,输出0
	func(1); // 传参时,使用指定的实参,输出1
	return 0;
}

输出如下:

2.缺省参数的分类

根据其缺省参数的个数,我们我可以将缺省参数分为全缺省半缺省。

2.1 全缺省

每一个参数都有缺省值。

#include<iostream>
using namespace std;

void func(int a = 0, int b = 1, int c = 2)
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl;
}

int main()
{
	func();				//不传参数
	func(10, 20);		//半传参数
	func(10, 20, 30);	//全传
	return 0;
}

输出结果:

2.2 半缺省

半缺省是部分形参给缺省值。C++规定半缺省参数必须从右往左依次连续缺省。

像这样:

#include<iostream>
using namespace std;

void func(int a, int b = 1, int c = 2)
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl;
}
int main()
{

	func(10, 20);		//半传参数
	cout << endl;
	func(10, 20, 30);	//全传
	return 0;
}

输出结果为:

3.注意事项

(1)带缺省参数的函数调用,C++规定必须从左到右依次给实参,不能跳跃给实参。

void func(int a ,int b=1,int c=2)
{
	cout <<" a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl;
}
func(,10,20)    //错误

(2)函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值

例如:我们在test.h中进行声明:

void Func(int a = 10);//声明

在test.cpp中定义:

void Func(int a = 20)    //定义
{
    cout << "a = " << a << endl;
}

这样是违反规定的。

函数重载

1.函数重载的定义

函数重载 (Function Overloading)是面向对象编程语言(如C++、Java、C#等)中的一个重要特性,它允许在同一个作用域内定义多个同名的函数 ,只要这些函数的参数列表(参数的类型、个数或顺序)不同即可。通过函数重载,可以实现功能相似但处理不同类型数据或不同数量参数的函数,而无需改变函数名

举个例子:

int Add(int x, int y)
{
	return x + y;
}

double Add(double x, double y)
{
	return x + y;
}

int Add(int x, int y, int z)
{
	return x + y + z;
}

2.函数重载的基本规则

(1)函数名必须相同

(2)参数列表必须不同

(3)作用域相同

(4)const和引用限定符不影响重载

此外,还有以下两点注意事项:

(1)仅仅只有返回类型不同,不能构成函数重载

int Add(int a, int b)
{
	return a + b;
}
// 错误
double Add(int a,int b)
{
	return a + b;
}

(2)缺省值不同不能构成函数重载

int Add(int a=1, int b=20)
{
	return a + b;
}
// 错误
int Add(int a=1, int b=2)
{
	return a + b;
}

3.函数重载的运用场景

(1)操作符重载: 虽然操作符重载严格来说不是函数重载的直接应用,但它利用了重载的原理。通过重载操作符(如 + 、 - 、 * 等),可以使自定义类型的使用更加直观和方便,就像使用内置类型一样。

(2)构造函数重载: 在类的设计中,构造函数重载允许为对象的初始化提供多种不同的方式。这不仅可以提高类的灵活性,还可以使类的使用更加直观。例如,一个表示日期的类可能提供多个构造函数,允许用户通过年、月、日来创建日期对象,或者通过另一个日期对象和一个时间间隔来创建新的日期对象。

(3)流插入和提取操作符的重载: 在C++标准库中,流插入操作符 << 和流提取操作符 >> 经常被重载,以便能够将这些操作符用于自定义类型。这使得将自定义类型的数据写入输出流或从输入流中读取自定义类型的数据变得非常简单和直观。

(4)通用函数:设计一组功能相似但处理不同类型数据的函数,通过函数重载提高代码的可读性和可维护性。

本篇博客主要讲的是第四种,其余三种将会在后面学习到。

引用

1.引用的概念

在C++中,引用(Reference)是一种复合类型,它提供了对另一个变量的直接访问能力,而不需要通过指针那样使用解引用操作符(*)。引用可以看作是某个变量的别名,对引用的操作实际上就是对它所引用的变量的操作。

其语法为:

引用对象类型& 引用变量名(对象名) = 引用实体;

引用类似于指针,因为指向同一块空间,所以改变引用变量引用实体也会改变。

来试着使用一下引用:

#include<iostream>
using namespace std;

int main()
{
	int a = 0;
	int& b = a;
	cout << &a << endl;
	cout << &b << endl;
	b++;
	cout << a << endl;
	cout << b << endl;
	return 0;
}

输出结果如下:

2.引用的主要特性

  • 必须初始化:引用在声明时必须被初始化,因为它没有独立的存储空间,只是另一个变量的别名。
  • 一旦绑定,不可改变:引用一旦被初始化后,就不能再指向另一个变量。尝试将引用重新绑定到另一个对象会导致编译错误。
  • 不占独立存储空间:引用本身不占用额外的存储空间,它只是提供了一个访问已存在变量的途径。
  • 引用传递:在函数调用时,可以通过引用传递参数,这样函数内对参数的修改会反映到原始变量上,而不需要通过指针来间接访问。
  • 引用作为返回值:函数可以返回引用,但需要注意返回局部变量的引用是危险的,因为局部变量在函数返回后会被销毁,导致返回的引用成为"悬挂引用"。

3.常引用

常引用(const引用)是一种特殊的引用,它指向的变量不能被修改。常引用可以用于函数参数和返回值,以保护数据不被意外修改。

举个例子:

int main()
{
	const int a = 0;	 
	const int& b = a;	

	// int& c = a;		
	// 错误:不能定义一个对常量的非常量引用c
	// 尝试修改常量的值是非法的  

	int c = 1;			 
	const int& d = c;	

	double pi = 3.14;	 

	// int& e = pi;		
	// 错误:不能定义一个整型引用e来引用浮点型变量pi。
	// 类型不匹配,且即使发生隐式类型转换
	// (这里不会发生,因为引用类型必须严格匹配),  
	// 引用也必须是指向相同类型的对象  
  
	const int& f = pi;
	// 正确:定义一个整型常量引用f来引用浮点型变量pi。
	// 这里发生了从double到int的隐式类型转换,但f是一个常量引用,  
	// 所以f不能用来修改pi的值
	// (即使pi是浮点型,但f引用的转换后的int值被视为常量)  
	return 0;
}

4.引用的使用场景

4.1 函数参数传递

通过引用传递函数参数是C++中常用的一种优化手段。

引用传递函数参数可以避免不必要的拷贝:当函数参数是大型对象或数据结构时,如果按值传递(即传递参数的副本),会导致在函数调用时复制整个对象。这不仅会消耗大量的内存和时间,还可能导致程序效率低下。而通过引用传递,函数直接接收原始对象的引用,而不是其副本,从而避免了不必要的拷贝操作。

示例:

void swap(int& x, int& y) 
{
	int tmp = x;
	x = y;
	y = tmp;
}

在这里,swap函数通过引用传递了两个整数参数x和y。在函数内部,它交换了这两个整数的值。由于使用了引用传递,所以交换操作直接作用于原始变量上,无需进行数据的复制。

4.2 函数返回值

函数可以返回引用,这样可以使函数返回一个可修改的对象,而不是返回对象的副本。这在重载赋值运算符和数组下标运算符等场景下非常有用。但需要注意,不能返回局部变量的引用,因为局部变量在函数返回后会被销毁,导致返回的引用成为"悬挂引用"

举个例子:

int& Fun()
{
	static int n = 0;
	n++;
	return n;
}

在这个例子中,由于n是静态的,它在程序的生命周期内始终存在,因此返回其引用是安全的。

5.引用和指针的差异

引用和指针在C++中都是用于间接访问数据的工具,但它们在使用上有一些差异。

5.1 定义和本质

引用:引用是已存在变量的别名。一旦引用被创建,它就绑定到了某个特定的变量上,并且在整个生命周期内都保持对该变量的引用。引用在定义时必须初始化,且一旦初始化后,就不能再改变为引用另一个变量。

指针:指针是存储变量地址的变量。指针可以指向任何类型的变量,包括其他指针。指针的值(即它所存储的地址)可以在程序执行过程中被改变,使其指向不同的变量或内存地址。

5.2 初始化和赋值

引用:引用在定义时必须初始化,且一旦初始化后,就不能再被重新绑定到另一个变量上。尝试将引用重新绑定到另一个变量将导致编译错误。

指针:指针可以在定义时初始化,也可以不初始化(此时它包含的是一个不确定的值,通常称为野指针)。指针的值(即它所指向的地址)可以在程序执行过程中被改变,使其指向不同的变量或内存地址。

5.3 空值

引用:引用不可以为空。一旦引用被创建,它就必须始终引用一个有效的变量。

指针:指针可以为空。空指针不指向任何有效的内存地址,通常用于表示指针当前不指向任何对象。

5.4 内存占用

引用:引用本身不占用额外的内存空间,它只是所引用变量的别名。因此,引用的大小与所引用变量的大小相同。

指针:指针本身占用一定的内存空间,用于存储它所指向变量的地址。在大多数现代计算机上,指针的大小通常是固定的(如4字节或8字节),与它所指向的变量大小无关。

5.5 安全性

引用:由于引用一旦初始化就不能改变其指向,且不存在空引用,因此引用在使用上相对更安全。引用减少了因指针操作不当而导致的错误(如野指针、空指针解引用等)。

指针:指针提供了更大的灵活性,但也带来了更高的风险。指针可以指向任何内存地址,包括未分配的内存或受保护的内存区域,这可能导致程序崩溃或安全问题。此外,指针还需要显式地进行内存管理(如分配和释放内存),这增加了出错的可能性。

内联函数

1.内联函数的定义

用inline修饰的函数叫做内联函数,编译时C++编译器会在调用的地方展开内联函数,这样调用内联函数就不需要建立栈帧了,就可以提高效率。

举个例子:

#include<iostream>
using namespace std;

inline int Add(int x, int y)
{
	return x + y;
}

int main()
{
	cout << Add(1, 2) << endl;
	return 0;
}

2.注意事项

(1)内联函数是一种以空间换时间 的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用。内联函数的优势减少了调用开销,提高程序运行效率,缺陷就是可能会使目标文件变大

(2)虽然使用了inline关键字,但编译器可能会忽略这个请求,特别是当函数体很大或包含复杂的控制结构时。编译器会根据自己的优化策略决定是否将函数内联。

(3)内联函数不能是递归的,因为内联展开会导致无限递归。

(4)inline不能声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。

nullptr空指针

NULL实际是⼀个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:

#ifndef NULL
    #ifdef __cplusplus
        #define NULL 0
    #else
        #define NULL ((void *)0)
    #endif
#endif

由此我们知道NULL既可以代表数字0,也可以代表空指针。这种模棱两可的定义就可能引出一些问题:

NULL 通常被定义为 (void*)0,这是一个指向 void 类型的空指针。然而,这种定义方式在某些情况下可能会引发类型安全问题,特别是当它被隐式转换为其他类型的指针时。此外,由于 NULL 是一个宏定义(在 C 和某些 C++ 环境中),它并不属于类型系统的一部分,这可能导致一些编译器警告或错误。

来看个例子:

#include<iostream>
using namespace std;

void func(int a)
{
	cout << "func(int)" << endl;
}

void func(int* p)
{
	cout << "func(int*)" << endl;
}

int main()
{
	func(0);
	func(NULL);
	func((int*)NULL);
	return 0;
}

输出结果为:

在默认情况下NULL被编译器当做数字0。这种问题是我们并不想看见的,所以 C++11 引入了nullptr来代替NULL。

结束语

开学了博客写的没有上个月快了。。。

不过我还是会坚持好好写的!!!

感谢各位大佬的支持!!!

希望各位大佬不要吝啬点赞收藏评论关注!!!

感谢!!!

相关推荐
Theodore_10222 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
‘’林花谢了春红‘’4 小时前
C++ list (链表)容器
c++·链表·list
----云烟----4 小时前
QT中QString类的各种使用
开发语言·qt
lsx2024064 小时前
SQL SELECT 语句:基础与进阶应用
开发语言
开心工作室_kaic5 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
向宇it5 小时前
【unity小技巧】unity 什么是反射?反射的作用?反射的使用场景?反射的缺点?常用的反射操作?反射常见示例
开发语言·游戏·unity·c#·游戏引擎
武子康5 小时前
Java-06 深入浅出 MyBatis - 一对一模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据仓库·sql·mybatis·springboot·springcloud
转世成为计算机大神5 小时前
易考八股文之Java中的设计模式?
java·开发语言·设计模式
机器视觉知识推荐、就业指导5 小时前
C++设计模式:建造者模式(Builder) 房屋建造案例
c++
宅小海6 小时前
scala String
大数据·开发语言·scala