初识C++(二)

一、函数重载

C++⽀持在同⼀作用域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者 类型不同。这样C++函数调⽤就表现出了多态行为为,使用更灵活。C语言是不支持同⼀作用域中出现同 名函数的。

函数重载的基本规则

1.函数名相同:所有重载的函数必须具有相同的函数名。

2.参数列表不同:参数列表必须不同,这可以通过改变参数的数目、类型或顺序来实现。仅通过改变函数返回类型来区分函数重载是不允许的。

3.可以改变返回类型:虽然返回类型不是区分重载函数的依据,但不同的重载函数可以有不同的返回类型。

4.可以抛出不同的异常:不同的重载函数可以声明抛出不同的异常,但这同样不是重载的决定性因素。

5.可以有不同的访问修饰符:比如,一个可以是public,另一个可以是private,但这与重载函数的解析无关。

函数重载示例如下:

cpp 复制代码
#include<iostream>  
using namespace std;

// 1、参数类型不同   
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;
}
cpp 复制代码
// 2、参数个数不同   
void f()
{
    cout << "f()" << endl;
}

void f(int a)
{
    cout << "f(int a)" << endl;
}
cpp 复制代码
// 3、参数类型顺序不同   
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;
}

void f1(int a = 0) 
{
    cout << "f1(int a = 0)" << endl; 
}
int main()
{
    Add(10, 20);
    Add(10.1, 20.2);
    f();
    f(10);
    f(10, 'a');
    f('a', 10);
    f1(); 
    f1(5); 
    return 0;
}

二、引用

在C++中,它是对C语言的重要扩充。引用可以被视为某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。这意味着,当你通过引用访问或修改数据时,实际上是在访问或修改它所引用的那个变量。

定义:

引用的定义方式与指针类似,但使用&符号来标识,而不是指针的*符号。引用的基本语法如下:

其中,&在此处不是求地址运算,而是起标识作用,表示这是一个引用声明。类型指的是目标变量的类型,引用名是你为这个变量定义的别名,而目标变量名则是被引用的原始变量名。

cpp 复制代码
#include<iostream>
using namespace std;
int main()
{
 int a = 0;
 // 引⽤:b和c是a的别名 
 int& b = a;
 int& c = a;
 // 也可以给别名b取别名,d相当于还是a的别名 
 int& d = b;
 ++d;
 // 这⾥取地址我们看到是⼀样的 
 cout << &a << endl;
 cout << &b << endl;
 cout << &c << endl;
 cout << &d << endl;
 return 0;
}

特性:

必须初始化:

引用在声明时必须被初始化,因为它必须是某个已存在变量的别名。你不能先声明一个引用,然后再去初始化它。

唯一性:一旦一个引用被初始化为某个变量的别名,它就不能再被改变为另一个变量的别名。
内存共享:

引用和它引用的变量共享同一块内存空间。这意味着,对引用的操作实际上就是对它所引用的变量的操作。
别名:

虽然从概念上讲,引用像是变量的一个别名,不占据额外的内存空间,但在底层实现上,编译器通常会将引用实现为指向位置不可变的指针(即const指针)。因此,引用在实际上也会占用一定的内存空间。
不支持数组:

不能直接定义引用的数组,因为数组是由多个元素组成的集合,而引用需要指向一个具体的变量。但是,可以定义数组的引用,即引用整个数组。

安全性:通过常引用(const引用),可以确保引用的变量不会被修改,从而增加代码的安全性。
多态:

引用是除指针外另一个可以产生多态效果的手段。一个基类的引用可以指向它的派生类实例,从而实现多态。

使用场景:

引用在C++中有广泛的应用场景,包括但不限于:

作为函数的参数,可以避免传递大型对象时的拷贝开销。

作为函数的返回值,可以返回函数内部变量的别名,但需要注意生命周期问题。

在需要别名的地方,如链表节点的指针可以使用引用来简化代码。

cpp 复制代码
#include <iostream>  
using namespace std;
void swap(int& x, int& y) {
    int temp = x;
    x = y;
    y = temp;
}

int main() {
    int a = 1, b = 2;
    cout << " swap: a = " << a << ", b = " << b << endl;
    swap(a, b);
    cout << " swap: a = " << a << ", b = " << b << endl;
    return 0;
}

三、const引用

const 引用是 C++ 中一种特殊的引用类型,它允许你创建一个对某个对象的引用,但通过这个引用你不能修改被引用的对象。这种机制主要用于保护数据不被意外修改,同时提供对数据的高效访问。

cpp 复制代码
const Type& name = originalObject;

四、指针和引用的关系

1. 语法概念和内存占用

指针:指针可以在声明时不初始化(但这是一个危险的做法,因为未初始化的指针可能指向任意内存位置),也可以在之后的任何时候修改其指向。
引用:引用必须在声明时被初始化,且一旦初始化,就不能再改变其指向(即不能再让引用指向另一个变量)。

2.初始化和修改

指针:指针可以在声明时不初始化(但这是一个危险的做法,因为未初始化的指针可能指向任意内存位置),也可以在之后的任何时候修改其指向。
引用:引用必须在声明时被初始化,且一旦初始化,就不能再改变其指向(即不能再让引用指向另一个变量)。

3. 访问方式

指针:访问指针指向的值时,需要使用解引用操作符*。
引用:引用就像直接使用该变量一样,不需要任何特殊的操作符。

4. sizeof 操作符

指针:无论指向何种类型的变量,指针本身的大小都是固定的(通常是4字节或8字节,取决于平台的地址空间大小)。

引用:在C++中,sizeof对引用的操作实际上返回的是被引用类型的大小,但这并不意味着引用占用了额外的空间,而是标准规定的一种处理方式。

5. 安全性和使用场景

指针:由于指针的灵活性,它们可以被用来实现各种复杂的数据结构和算法,但这也带来了更高的出错风险,如空指针解引用、野指针等问题。
引用:引用提供了一种更安全、更简洁的方式来访问和修改数据,特别是在函数参数传递和返回值时。使用引用可以避免不必要的拷贝,提高效率,同时减少出错的机会。

五、inline函数

定义:

使用inline关键字声明的函数称为内联函数。编译器在编译时尝试将内联函数的调用替换为函数体本身,以减少函数调用的开销。
优点: 可以减少函数调用的开销,提高程序运行效率。
缺点: 如果过度使用或在不适当的情况下使用(如大型函数),可能会导致编译后的代码体积增大,反而降低性能。

与宏的比较
宏: 在C语言中,宏通过预处理器在编译前进行文本替换。这种方法虽然灵活,但容易出错,且不易调试。
inline函数:C++引入inline作为宏的一种更安全、更易于维护的替代方案。与宏相比,inline函数具有类型检查、作用域限制等编译时检查,使得代码更加健壮和易于理解。

编译器行为
不同编译器的差异 :不同的编译器对inline的实现和支持程度可能有所不同。有些编译器可能更积极地内联函数,而有些则可能更加保守。
调试版本:在调试(Debug)模式下,许多编译器默认不内联函数,以便更好地进行调试。如果需要在调试模式下内联函数,可能需要通过编译器的特定选项来启用。

vs编译器debug版本下⾯默认是不展开inline的,这样方便调试,debug版本想展开需要设置⼀下 以下两个地方。

cpp 复制代码
#include<iostream>
using namespace std;
inline int Add(int x, int y)
{
 int ret = x + y;
 ret += 1;
 ret += 1;
 ret += 1;
 return ret;
}
int main()
{
 // 可以通过汇编观察程序是否展开 
 // 有call Add语句就是没有展开,没有就是展开了 
 int ret = Add(1, 2);
 cout << Add(1, 2) * 5 << endl;
 return 0;
}
cpp 复制代码
#include<iostream>
using namespace std;
// 实现⼀个ADD宏函数的常⻅问题 
//#define ADD(int a, int b) return a + b;
//#define ADD(a, b) a + b;
//#define ADD(a, b) (a + b)
// 正确的宏实现 
#define ADD(a, b) ((a) + (b))
// 为什么不能加分号? 
// 为什么要加外⾯的括号? 
// 为什么要加⾥⾯的括号? 
int main()
{
 int ret = ADD(1, 2);
 cout << ADD(1, 2) << endl;
 cout << ADD(1, 2)*5 << endl;
 int x = 1, y = 2;
 ADD(x & y, x | y); // -> (x&y+x|y)
 return 0;
}
cpp 复制代码
// F.h
#include <iostream>
using namespace std;
inline void f(int i);
// F.cpp
#include "F.h"
void f(int i)
{
 cout << i << endl;
}
// main.cpp
#include "F.h"
int main()
{
// 链接错误:⽆法解析的外部符号 "void __cdecl f(int)" (?f@@YAXH@Z) 
 f(10);

 return 0;
}

六、 nullptr

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

cpp 复制代码
#ifndef NULL
 #ifdef __cplusplus
 #define NULL 0
 #else
 #define NULL ((void *)0)
 #endif
#endif

nullptr 是 C++11 引入的一个关键字,用于表示空指针常量。在 C++11 之前,空指针通常使用字面量 0 或宏 NULL(定义为 (void*)0 或简单的 0,具体取决于编译器和标准库的实现)来表示。然而,0 和 NULL 都存在类型不明确的问题,因为它们可以被隐式转换为整数或其他指针类型,这可能导致类型安全上的隐患。

nullptr 解决了这个问题,它是一个指针字面量,其类型为 std::nullptr_t,但可以被隐式转换为任何原始指针类型。使用 nullptr 可以使代码更加清晰和类型安全,因为它明确表示了一个空指针,而不是整数或其他类型的值。

cpp 复制代码
int* ptr = nullptr; // ptr 是一个指向 int 的空指针  
if (ptr == nullptr) {  
    // ptr 是空指针,执行相应逻辑  
}  
  
// 假设有一个函数,其参数是指向 int 的指针  
void func(int* p) {  
    if (p == nullptr) {  
        // 处理空指针的情况  
    }  
    // ...  
}  
  
// 调用 func,传入 nullptr  
func(nullptr);
相关推荐
m0_631270401 小时前
标准C++(二)
开发语言·c++·算法
Zhen (Evan) Wang1 小时前
What is the new in C#11?
开发语言·c#
沫刃起1 小时前
Codeforces Round 972 (Div. 2) C. Lazy Narek
数据结构·c++·算法
0224号比邻星1 小时前
[C语言]第十节 函数栈帧的创建和销毁一基础知识到高级技巧的全景探索
c语言·开发语言
爱coding的橙子1 小时前
CCF-CSP认证考试准备第十五天 202303-3 LDAP
算法
GZM8888882 小时前
配置VS Code以进行C/C++编程:深入探讨与实操指南
c++
martian6652 小时前
学懂C++(六十):C++ 11、C++ 14、C++ 17、C++ 20新特性大总结(万字详解大全)
开发语言·c++·c++20
QXH2000002 小时前
Leetcode—环形链表||
c语言·数据结构·算法·leetcode·链表
zhangbin_2372 小时前
【Python机器学习】NLP信息提取——命名实体与关系
开发语言·人工智能·python·深度学习·机器学习·自然语言处理
小灰灰爱代码3 小时前
C++——判断year是不是闰年。
数据结构·c++·算法