深入计算机语言之C++:C到C++的过度-2

简介: 深入计算机语言之C++:C到C++的过度-2

深入计算机语言之C++:C到C++的过度-1

https://developer.aliyun.com/article/1624600


七、函数重载

函数重载是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这

些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型

不同的问题。


可以通过参数类型,参数个数,参数类型顺序来构成函数重载:

int Add(int a, int b)
{
  return a + b;
}
double Add(double a, double b)//类型不同
{
  return a + b;
}
int Add(int a, int b, int c)//个数不同
{
  return a + b;
}
 
 
int Add(char a, int c)
{
  return a + c;
}
int Add(int a, char c)//类型顺序不同
{
  return a + c;
}
 


注意:

  1. 返回值类型不同无法构成函数重载
  2. 缺省值不同也不能构成函数重载


八、引用

8.1 引用的概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空

间,它和它引用的变量共用同一块内存空间 。其语法为:


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

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

#include<iostream>
using namespace std;
int main()
{
  int a = 1;
  int& b = a;//引用
  int& c = b;
  cout << &a << endl;
  cout << &b << endl;
  cout << &c << endl;
  c++;
  cout << a << endl;
  cout << b << endl;
  cout << c << endl;
  return 0;
}


8.2 引用的特性

8.2.1 引用时必须初始化

int& b;//错误的,必须初始化
 
int& b = a;


8.2.2 一个变量可以有多个引用(给别名取别名)

int a = 1;
int& b = a;
int& c = a;//多个引用


8.2.3 引用一旦引用一个实体,再不能引用其他实体

int a = 1;
int& b = a;
b = 2;//这时是赋值,相当于a = b = 2;


8.3 引用的使用

8.3.1 作为函数的参数

#include<iostream>
using namespace std;
 
void swap(int& a, int& b)
{
  int z = a;
  a = b;
  b = z;
}
 
int main()
{
  int a = 1, b = 2;
  swap(a, b);
  cout << "a = " << a << endl;
  cout << "b = " << b << endl;
 
    return 0;
}

8.3.2 做函数返回值

函数的返回值是存储在一个临时的变量里面。这个变量正常情况下是不可修改的,可以看作一个常量,我们不能对常量进行赋值。


但使用引用作为返回值相当于返回一个引用,没有中间拷贝过程和临时变量,进而同时改变了引用对象(func1(a))和被引用对象(a)。

#include<iostream>
using namespace std;
 
int& func1(int& a)
{
  a++;
  return a;
}
 
int main()
{
  int a = 1;
  func1(a) = 10;
  cout << a;
  return 0;
}


8.3.3 错误示范

1. 引用指向的空间栈帧销毁

int& func()
{
    int a = 0;
    return a;
}


返回了a的引用,但当离开函数,函数的栈帧销毁,相当于返回了一个野指针

2. 引用指向的函数多次调用

int& Add(int a, int b)
{
  int c = a + b;
  return c;
}
int main()
{
  int& ret = Add(1, 2);
  Add(3, 4);
  cout << ret <<endl;
  return 0;
}//输出什么


输出结果为:7

那是因为在第二次调用函数Add(3,4)时,会在原来第一次调用Add(1,2)建立栈帧的空间上建立栈帧所以返回值c的值会被重新覆盖,ret是指向Add所在位置的栈帧的别名,所以ret值也会发生改变。

8.4 const引用

8.4.1 对常变量的引用(权限不可放大)

我们可以通过 const 修饰引用来让其变为常引用。这时引用变量是不能被修改的,并且只能将常变量复杂给常引用,不能将常变量赋值给引用,必须用const来引用

const int a = 10;
// 编译报错:error C2440: “初始化”: ⽆法从“const int”转换为“int &” 
// 这⾥的引⽤是对a访问权限的放⼤
//int& ra = a;
 
//这样才可以
const int& a = 10;
// 编译报错:error C3892: “ra”: 不能给常量赋值 
//ra++;


8.4.2 const引用普通变量(权限可缩小)

const 引⽤也可以引⽤普通变量,因为对象的访问权限在引⽤过程中可以缩⼩,但是不能放⼤。

// 这⾥的引⽤是对b访问权限的缩⼩
int b = 20; const int& rb = b;
// 编译报错:error C3892: “rb”: 不能给常量赋值
//rb++;


8.4.3 const可以引用含有常性的对象

含有常性的变量包括常数,函数返回值等。

const int& ra = 30;


int a = 1, b = 2;
const int& ra = a * 3;
const int& rb = a + b;


double d = 12.34; 
// 编译报错:“初始化”: ⽆法从“double”转换为“int &” 
// int& rd = d;
int rc = d;//隐式类型转换
const int& rd = d;


不需要注意的是类似 int& ra = a*3; int& rb = a + b; int& rd = d; 这样⼀些场景下 a*3 的运算结果和 a 保存在⼀个临时对象中。 int& rd = d 也是类似,引用时进行类型转换被称为隐式类型转换,在类型转换中会产⽣临时对象存储中间值。也就是此时,ra 和 rd 引⽤的都是临时对象,⽽C++规定临时对象具有常性,所以这⾥ 就触发了权限放⼤,必须要⽤常引⽤才可以。


所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象, C++中把这个未命名对象叫做临时对象。


注:

const int a = 10;
int& ra = a;//权限放大
int rb = a;//权限没有放大

a处于一块临时对象,只拥有读取权限,没有写入权限,此时 int& ra 是指向a所在的空间(别名),要求读取和写入的权限,所以就产生了权限放大。第二个只是将 a 的值读取拷贝给 rb,并没有产生权限放大。

const 引用传参在未来学习模板类的时候会有进行运用。


8.5 引用与指针的区别

  1. 语法概念上引⽤是⼀个变量的取别名不开空间,指针是存储⼀个地址的变量,要开空间。
  2. 引⽤在定义时必须初始化,指针建议初始化,但是语法上不是必须的。
  3. 引⽤在初始化时引⽤⼀个对象后,就不能再引⽤其他对象;⽽指针可以在不断地改变指向对象。
  4. 引⽤可以直接访问指向对象,指针需要解引⽤才是访问指向对象。
  5. sizeof中含义不同,引⽤结果为引⽤类型的⼤⼩,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8byte)
  6. 指针很容易出现空指针和野指针的问题,引⽤很少出现,引⽤使⽤起来相对更安全⼀些。


九、内联函数

在C语言中,无论宏常量还是宏函数虽能提升程序运行效,但都有易出错,无法调试等缺陷。而C++为了弥补这一缺陷,引入了内联函数的概念代替宏函数。


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

#include<iostream>
using namespace std;
inline int Add(int x, int y)
{
  return x + y;
}
int main()
{
  Add(1, 2);
  return 0;
}

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

C/C++:常规——调试信息格式改成程序数据库,优化——内联函数扩展改成只适用于_inline

注意:

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


因为内联函数会在调用时直接展开,编译器默认认为不需要地址,如果声明与定义分离内联函数的地址根本不会进入符号表,链接时就无法找到定义的函数,就会发生链接错误。


十、nullptr

在C语言中,定义了一个宏NULL,在传统的C头文件(stddef.h)中,可以看到如下代码 :

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

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

#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当成一个指针,但是在默认情况下NULL被编译器当做数字0。这种问题是我们并不想看见的,所以C++11引入了nullptr来代替NULL。


C++11中引⼊nullptr,nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字⾯量,它可以转换成任意其他类型的指针类型。使⽤nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,⽽不能被转换为整数类型

相关文章
|
4月前
|
C++
C++ 语言异常处理实战:在编程潮流中坚守稳定,开启代码可靠之旅
【8月更文挑战第22天】C++的异常处理机制是确保程序稳定的关键特性。它允许程序在遇到错误时优雅地响应而非直接崩溃。通过`throw`抛出异常,并用`catch`捕获处理,可使程序控制流跳转至错误处理代码。例如,在进行除法运算或文件读取时,若发生除数为零或文件无法打开等错误,则可通过抛出异常并在调用处捕获来妥善处理这些情况。恰当使用异常处理能显著提升程序的健壮性和维护性。
76 2
|
4月前
|
算法 C语言 C++
C++语言学习指南:从新手到高手,一文带你领略系统编程的巅峰技艺!
【8月更文挑战第22天】C++由Bjarne Stroustrup于1985年创立,凭借卓越性能与灵活性,在系统编程、游戏开发等领域占据重要地位。它继承了C语言的高效性,并引入面向对象编程,使代码更模块化易管理。C++支持基本语法如变量声明与控制结构;通过`iostream`库实现输入输出;利用类与对象实现面向对象编程;提供模板增强代码复用性;具备异常处理机制确保程序健壮性;C++11引入现代化特性简化编程;标准模板库(STL)支持高效编程;多线程支持利用多核优势。虽然学习曲线陡峭,但掌握后可开启高性能编程大门。随着新标准如C++20的发展,C++持续演进,提供更多开发可能性。
86 0
|
2月前
|
算法 C++
2022年第十三届蓝桥杯大赛C/C++语言B组省赛题解
2022年第十三届蓝桥杯大赛C/C++语言B组省赛题解
47 5
|
2月前
|
存储 编译器 C语言
深入计算机语言之C++:类与对象(上)
深入计算机语言之C++:类与对象(上)
|
2月前
|
编译器 Linux C语言
深入计算机语言之C++:C到C++的过度-1
深入计算机语言之C++:C到C++的过度-1
|
3月前
|
JavaScript 前端开发 测试技术
一个google Test文件C++语言案例
这篇文章我们来介绍一下真正的C++语言如何用GTest来实现单元测试。
24 0
|
4月前
|
编译器 C++ 容器
C++语言的基本语法
想掌握一门编程语言,第一步就是需要熟悉基本的环境,然后就是最重要的语法知识。 C++ 程序可以定义为对象的集合,这些对象通过调用彼此的方法进行交互。现在让我们简要地看一下什么是类、对象,方法、即时变量。 对象 - 对象具有状态和行为。例如:一只狗的状态 - 颜色、名称、品种,行为 - 摇动、叫唤、吃。对象是类的实例。 类 - 类可以定义为描述对象行为/状态的模板/蓝图。 方法 - 从基本上说,一个方法表示一种行为。一个类可以包含多个方法。可以在方法中写入逻辑、操作数据以及执行所有的动作。 即时变量 - 每个对象都有其独特的即时变量。对象的状态是由这些即时变量的值创建的。 完整关键字
|
5月前
|
前端开发 编译器 程序员
协程问题之为什么 C++20 的协程代码比其他语言的协程 demo 长很多如何解决
协程问题之为什么 C++20 的协程代码比其他语言的协程 demo 长很多如何解决
|
12天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
25 2
|
18天前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
53 5