1 引用

1.1 定义及编程实践

引用,是某个已存在变量的另一个名字。

一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。

注意:

  • 引用没有定义,是一种关系型声明。声明它和原有某一变量(实体)的关

    系。因此引用类型必须与原类型保持一致,且不分配内存。与被引用的变量有相同的地

    址。

  • & 符号前有数据类型时,是引用。其它皆为取地址。

  • 可对引用再次引用。多次引用的结果,是某一变量具有多个别名。

创建引用例子如下:

// reference.cpp

#include <iostream>

using namespace std;

int main ()
{
// 声明简单的变量
int i;
double d; // 声明引用变量
int& r = i;
double& s = d; i = 1;
cout << "Value of i : " << i << endl;
cout << "Value of i reference : " << r << endl; d = 6.1;
cout << "Value of d : " << d << endl;
cout << "Value of d reference : " << s << endl; getchar(); return 0;
}

运行结果:

引用编程实践如下:

// reference2.cpp

#include <iostream>

using namespace std;

int main()
{
int a = 1; int& b = a; // b = a = 1 a = 2; int *p = &a; *p = 3; // a = 3 cout << "a = " << a << endl; b = 4; // b = a -> a = 4 cout << "a = " << a << ", b = " << b << endl; getchar(); return 0;
}

运行结果:

1.2 引用和指针的区别

引用很容易和指针混淆,它们之间有以下不同:

  • 不存在空引用。引用必须连接到一块合法的内存。指针可以为空指针。

  • 引用必须在创建时被初始化(引用作为函数参数的时候不需要初始化,因为形参一定会被赋值的)。指针可以在任何时间被初始化。

  • 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。

1.3 引用的意义

  1. 引用作为其他变量的别名而存在,因此在一些场合可以代替指针

  2. 引用相对于指针来说具有更好的可读性和实用性

// 无法实现两数据的交换
void swap(int a,int b);
//开辟了两个指针空间用于交换
void swap(int *a,int *b);
// referenceSwap.cpp,不开辟空间使用引用进行数值交换
#include <iostream> using namespace std; void swap(int& a, int& b)
{
int temp; temp = a;
a = b;
b = temp;
} int main()
{
int a = 1,b = 2; cout << "a = " << a << " , b = " << b << endl; swap(a,b); cout << "a = " << a << " , b = " << b << endl; getchar(); return 0;
}

运行结果:

1.4 堆变量,栈变量

全局变量、静态局部变量、静态全局变量,new 产生的变量都在堆中,动态分配的变量在堆中分配。

局部变量在栈里面分配。

程序为栈变量分配动态内存,在程序结束为栈变量清除内存,但是堆变量不会被清除。

1.5 引用作为函数的返回值

当函数返回值为引用时:

  1. 若返回栈变量引用时,不能成为其他引用的初始值。

如下代码所示:

// funcReturnRef.cpp,引用作为函数的返回值,什么时候可以为其他引用初始化的值
#include <iostream> using namespace std; // 返回栈变量
int getA1()
{
int a; a = 1; return a;
} // 返回栈变量引用
int& getA2()
{
int a; a = 1; return a;
} int main()
{
int a1 = 0;
int a2 = 0; // 值拷贝
a1 = getA1(); // 将 栈变量引用 赋值给 变量,编译器类似做了如下隐藏操作:a2 = *(getA2())
a2 = getA2(); // 将 栈变量引用 赋值给 另一个引用作为初始值。此时将会有警告:返回局部变量或临时变量的地址
int& a3 = getA2(); cout << "a1 = " << a1<< endl;
cout << "a2 = " << a2<< endl;
cout << "a3 = " << a3<< endl; getchar(); return 0;
}

警告信息:

第一次运行结果:

第二次运行结果:

  1. 若返回堆变量引用时,可以成为其他引用的初始值

如下代码所示:

// funcReturnRef2.cpp,引用作为函数的返回值,什么时候可以为其他引用初始化的值
#include <iostream> using namespace std; // 返回堆变量
int getA1()
{
static int a; a = 1; return a;
} // 返回栈变量引用
int& getA2()
{
static int a; a = 1; return a;
} int main()
{
int a1 = 0;
int a2 = 0; // 值拷贝
a1 = getA1(); // 将 栈变量引用 赋值给 变量,编译器类似做了如下隐藏操作:a2 = *(getA2())
a2 = getA2(); // 将 堆变量引用 赋值给 另一个引用作为初始值。由于是静态区域,地址不变,内存合法。
int& a3 = getA2(); cout << "a1 = " << a1<< endl;
cout << "a2 = " << a2<< endl;
cout << "a3 = " << a3<< endl; getchar(); return 0;
}

运行结果:

1.6 指针引用作函数参数

C++ 中指针引用作函数参数,与 C 语言中二级指针作函数参数的区别。

如下代码所示:

// pointReference.cpp
// C++ 中指针引用作函数参数,与 C 语言中二级指针作函数参数的区别 #include <iostream> using namespace std; #define AGE 18 // C 语言中的二级指针
int getAge1(int **p)
{
int age = AGE; int *ptemp = &age; // p 是实参的地址, *实参的地址,去间接的修改实参
*p = ptemp; return 0;
} // C++ 中指针引用
int getAge2(int* &p)
{
int age = AGE; if(p == NULL)
{
p = (int *)malloc(sizeof(int)); if(p == NULL)
return -1;
} // 给 p 赋值,相当于给 main 函数中的 pAge 赋值
*p = age; return 0;
} int main(void)
{
int *pAge = NULL; // 1 C 语言中二级指针
getAge1(&pAge);
cout << "age: " << *pAge << endl;
pAge = NULL; // C++ 中指针引用
getAge2(pAge);
cout << "age: " << *pAge << endl;
pAge = NULL; getchar(); return 0;
}

运行结果:

1.7 const 引用

  1. const 对象的引用必须是 const 的。

  2. const 引用可以使用相关类型的对象(常量,非同类型的变量或表达式)初始化。这个是 const 引用与普通引用最大的区别。

例:

const int &a = 1;
double x = 1.1;
const int &b = x;
  1. const 引用限制对象为只读,不能通过修改 const 引用来修改 const 对象。

2 inline 内联函数

C 语言中有宏函数的概念。宏函数的特点是内嵌到调用代码中去,避免了函数调用的开销。但是由于宏函数的处理发生在预处理阶段,确实了语法检测和有可能带来的语义差错,因此 C++ 引入了 inline 内联函数。

2.1 内联函数基本概念

  1. 内联函数声明时 inline 关键词必须和函数定义结合在一起,否则编译器会忽略内联请求。

  2. C++ 编译器直接将函数体插入在函数调用的地方。

  3. 内联函数没有普通函数调用时的额外开销(压栈,跳转,返回)

  4. 内联函数是一种特殊的函数,具有普通函数的特征(参数检查,返回类型等)

  5. 内联函数由编译器处理,直接将编译后的函数体插入在调用的地方,宏函数由预处理器处理,进行简单的文本替换,没有任何的编译过程。

  6. C++ 对内联函数的限制:

    • 不能存在任何形式的循环语句
    • 不能存在过多的条件判断语句
    • 函数体不能过于庞大
    • 不能对函数进行取址操作
    • 函数内联声明必须在调用语句之前
  7. 编译器对于内联函数的限制不是绝对的,内联函数相对于普通函数的优势知识节省了函数调用时压栈,跳转,和返回的开销。因此,当函数体的执行开销远大于压栈,跳转,和返回的开销时,那么内联函数将没有意义。

实例代码如下所示:

// inlineFunction.cpp
// 内联函数示例 #include <iostream> using namespace std; inline void func()
{
cout << "this is inlineFunction example!" << endl;
} int main()
{
func(); getchar(); return 0;
}

运行结果:

3 函数重载

函数重载:用同一个函数名定义不同的函数,当函数名和不同的参数搭配时函数的含义不同。

3.1 重载规则

  1. 函数名相同

  2. 参数个数不同,参数的类型不同,参数顺序不同,均可构成重载。

  3. 返回值类型不同则不可以构成重载。

如下所示:

void func(int a);         // ok
void func(char a); // ok
void func(char a,int b); // ok
void func(int a,char b); // ok
char func(int a); // 与第一个函数冲突,报错

3.2 调用准则

  1. 严格匹配,找到即调用。

  2. 通过隐式转换寻求一个匹配,找到即调用。

3.3 重载底层实现

C++ 利用 name mangling(倾轧)技术,来改变函数名,以区分参数不同的同名函数。

实现原理:用 v c i f l d 表示 void char int float long double 及其引用。

如下所示:

void func(char a);  // func_c(char a);
void func(char a,int b,double c); // func_cid(char a,int b,double c);

3.4 函数指针基本语法

// 方法一:
// 声明一个函数类型
typedef void (myfunctype)(int a,int b);
// 定义一个函数指针
myfunctype* fp1= NULL; // 方法二:
// 声明一个函数指针类型
typedef void (*myfunctype_pointer)(int a,int b)
// 定义一个函数指针
myfunctype_pointer fp2 = NULL; // 方法三:
// 直接定义一个函数指针
void (*fp3 )(int a,int b);

3.5 函数重载和函数指针结合

当使用重载函数名对函数指针进行赋值时,根据重载规则挑选与函数指针参数列表一致的候选者,严格匹配候选者的函数类型与函数指针的函数类型。

示例代码如下所示:

#include <iostream>

using namespace std;

void func(int a, int b)
{
cout << a << b << endl;
} void func(int a, int b, int c)
{
cout << a << b << c << endl;
} void func(int a, int b, int c, int d)
{
cout << a << b << c << d << endl;
} // 1 定义一个函数类型
typedef void(myfunctype)(int, int); //定义了一个函数类型, 返回值void 参数列表是 int,int ,, void()(int,int) // 2 定义一个函数指针类型
typedef void(*myfunctype_pointer)(int, int); //定义了一个函数指针类型, 返回值void 参数列表是 int,int ,, void(*)(int,int) int main(void)
{
//1 定义一个函数指针
myfunctype * fp1 = NULL; fp1 = func; fp1(10, 20); // 2 定义一个函数指针
myfunctype_pointer fp2 = NULL; fp2 = func; fp2(10, 20); // 3 直接定义一个函数指针
void(*fp3)(int, int) = NULL; fp3 = func; fp3(10, 20); cout << " -----------------" << endl; // 此时的fp3 是 void(*)(int,int)
// fp3(10, 30, 30); // fp3 恒定指向一个 函数入口,void func(int, int) 的函数入口
// fp3(10, 30, 40, 50); // 想要通过函数指针,发生函数重载 是不可能。
fp3(10, 20); void(*fp4)(int, int, int) = func; // 在堆函数指针赋值的时候,函数指针会根据自己的类型 找到一个重载函数 fp4(10, 10, 10);
// fp4(10, 10, 10, 10);
// 函数指针,调用的时候是不能够发生函数重载的。 void(*fp5)(int, int, int, int) = func; // void func(int ,int ,int ,int )
fp5(10, 10, 10, 10); return 0;
}

运行结果:

3.6 函数重载总结

  • 重载函数在本质上是相互独立的不同函数。

  • 函数重载是由函数名和参数列表决定的。

C++ 基础 2:C++ 对 C 语言的拓展的更多相关文章

  1. 【个人笔记】002-PHP基础-01-PHP快速入门-02-PHP语言相关介绍输

    002-PHP基础-01-PHP快速入门 02-PHP语言相关介绍 1.PHP是什么 Hypertext Preprocessor超文本预处理器 是一种通用开源脚本语言 Personal Home P ...

  2. 01 C语言程序设计--01 C语言基础--第1章 C语言概述&第2章 GCC和GDB

    走进嵌入式开发的世界,企业级项目课程让你达到企业嵌入式应用开发要求.名师在线答疑,解决疑难.科学评测体系,系统评估学习.核心项目实........ 30 门课程 241小时12分钟 824 人学习 学 ...

  3. 安装完最小化 RHEL/CentOS 7 后需要做的 30 件事情7. 安装 PHP PHP 是用于 web 基础服务的服务器端脚本语言。它也经常被用作通用编程语言。在最小化安装的 CentOS 中安

    CentOS 是一个工业标准的 Linux 发行版,是红帽企业版 Linux 的衍生版本.你安装完后马上就可以使用,但是为了更好地使用你的系统,你需要进行一些升级.安装新的软件包.配置特定服务和应用程 ...

  4. Microsoft Translator:打破语言障碍 拓展全球沟通新机遇

    Translator:打破语言障碍 拓展全球沟通新机遇"> 作者:Olivier Fontana, 微软研究院Microsoft Translator产品战略总监 世界越来越小,全球协 ...

  5. Linux基础与Linux下C语言编程基础

    Linux基础 1 Linux命令 如果使用GUI,Linux和Windows没有什么区别.Linux学习应用的一个特点是通过命令行进行使用. 登录Linux后,我们就可以在#或$符后面去输入命令,有 ...

  6. java基础学习总结一(java语言发展历史、jdk的下载安装以及配置环境变量)

    最近一段时间计划复习一下java基础知识,使用的视频课程是尚学堂高淇老师的,上课过程中的心得体会直接总结一下,方便以后复习. 一:计算机语言的发展 1:机器语言,最原始的语言,主要有“01”构成,最早 ...

  7. 零基础数据分析与挖掘R语言实战课程(R语言)

    随着大数据在各行业的落地生根和蓬勃发展,能从数据中挖金子的数据分析人员越来越宝贝,于是很多的程序员都想转行到数据分析, 挖掘技术哪家强?当然是R语言了,R语言的火热程度,从TIOBE上编程语言排名情况 ...

  8. Java基础笔记(1)----语言基础

    变量 变量:是内存中的一块存储空间,是存储数据的基本单元. 使用:先声明,后赋值,在使用. 声明:数据类型 + 变量名 = 值.(例:int a = 5:) 数据类型 分类:如图: 详解: Strin ...

  9. 零基础学Python--------第2章 Python语言基础

    第2章  Python语言基础 2.1 Python语法特点 2.11注释 在Python中,通常包括3种类型的注释,分别是单行注释.多行注释和中文编码声明注释. 1.单行注释 在Python中,使用 ...

  10. 【转】Linux基础与Linux下C语言编程基础

    原文:https://www.cnblogs.com/huyufeng/p/4841232.html ------------------------------------------------- ...

随机推荐

  1. 开始接触flex

    flex框架使用的是.mxml后缀的文件,可以在Eclipse导入flex开发的插件.代码写完之后需要进行编译成为.swf文件成功之后才可以正常运行.现在刚开始接触金融的项目,需求什么的还有很多不是理 ...

  2. DE2资源集锦

    1.The School of Electrical and Computer Engineering (ECE) at the Georgia Institute of Technology:htt ...

  3. linux块设备驱动---相关结构体(转)

    上回最后面介绍了相关数据结构,下面再详细介绍 块设备对象结构 block_device 内核用结构block_device实例代表一个块设备对象,如:整个硬盘或特定分区.如果该结构代表一个分区,则其成 ...

  4. eclipse 配置opencv

    1 准备 eclipse 2017 JDK1.8 opencv 4.40 2 配置 新建java工程 添加jar包 选择opencv-xxx.jar包 加入原生库 选择原生库位置 确认即可,测试 新建 ...

  5. 多测师讲解selenium_输入性弹框定位_高级讲师肖sir

    #输入性弹框from selenium import webdriverfrom time import sleepdrvier=webdriver.Chrome()url='file:///F:\d ...

  6. 习题3-3 数数字(Digit Counting , ACM/ICPC Danang 2007, UVa1225)

    #include<stdio.h> #include<string.h> int main() { char s[100]; scanf("%s",s); ...

  7. C语言和单片机C语言为什么会有差异?虽不同但理同!

    许多小伙伴在学完C语言后想入门单片机,但学着学着发现明明都是C语言,为什么单片机C语言和我当初学的C语言有差异呢? 今天小编就来梳理我们平时所学的C语言与单片机C语言的有什么样的不同. 单片机c语言比 ...

  8. js 无刷新文件上传 (兼容IE9 )

    之前项目中有个文件上传了需求,于是直接就使用了FormData对象异步上传,但是在测试得时候发现ie9无法正常上传(项目要求兼容IE9+),无奈,查资料得知IE9- 版本不支持formdata对象得异 ...

  9. 搭建分布式 Redis Cluster 集群与 Redis 入门

    目录 Redis 集群搭建 Redis 是啥 集群(Cluster) Redis Cluster 说明 Redis Cluster 节点 Redis Cluster 集群模式 不能保证一致性 创建和使 ...

  10. go xpath

    package main import ( "fmt" "github.com/antchfx/htmlquery" "net/http" ...