C++ 基础 2:C++ 对 C 语言的拓展
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 引用的意义
引用作为其他变量的别名而存在,因此在一些场合可以代替指针
引用相对于指针来说具有更好的可读性和实用性
// 无法实现两数据的交换
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 引用作为函数的返回值
当函数返回值为引用时:
- 若返回栈变量引用时,不能成为其他引用的初始值。
如下代码所示:
// 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;
}
警告信息:
第一次运行结果:
第二次运行结果:
- 若返回堆变量引用时,可以成为其他引用的初始值
如下代码所示:
// 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 引用
const 对象的引用必须是 const 的。
const 引用可以使用相关类型的对象(常量,非同类型的变量或表达式)初始化。这个是 const 引用与普通引用最大的区别。
例:
const int &a = 1;
double x = 1.1;
const int &b = x;
- const 引用限制对象为只读,不能通过修改 const 引用来修改 const 对象。
2 inline 内联函数
C 语言中有宏函数的概念。宏函数的特点是内嵌到调用代码中去,避免了函数调用的开销。但是由于宏函数的处理发生在预处理阶段,确实了语法检测和有可能带来的语义差错,因此 C++ 引入了 inline 内联函数。
2.1 内联函数基本概念
内联函数声明时 inline 关键词必须和函数定义结合在一起,否则编译器会忽略内联请求。
C++ 编译器直接将函数体插入在函数调用的地方。
内联函数没有普通函数调用时的额外开销(压栈,跳转,返回)
内联函数是一种特殊的函数,具有普通函数的特征(参数检查,返回类型等)
内联函数由编译器处理,直接将编译后的函数体插入在调用的地方,宏函数由预处理器处理,进行简单的文本替换,没有任何的编译过程。
C++ 对内联函数的限制:
- 不能存在任何形式的循环语句
- 不能存在过多的条件判断语句
- 函数体不能过于庞大
- 不能对函数进行取址操作
- 函数内联声明必须在调用语句之前
编译器对于内联函数的限制不是绝对的,内联函数相对于普通函数的优势知识节省了函数调用时压栈,跳转,和返回的开销。因此,当函数体的执行开销远大于压栈,跳转,和返回的开销时,那么内联函数将没有意义。
实例代码如下所示:
// 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 重载规则
函数名相同
参数个数不同,参数的类型不同,参数顺序不同,均可构成重载。
返回值类型不同则不可以构成重载。
如下所示:
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 调用准则
严格匹配,找到即调用。
通过隐式转换寻求一个匹配,找到即调用。
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 语言的拓展的更多相关文章
- 【个人笔记】002-PHP基础-01-PHP快速入门-02-PHP语言相关介绍输
002-PHP基础-01-PHP快速入门 02-PHP语言相关介绍 1.PHP是什么 Hypertext Preprocessor超文本预处理器 是一种通用开源脚本语言 Personal Home P ...
- 01 C语言程序设计--01 C语言基础--第1章 C语言概述&第2章 GCC和GDB
走进嵌入式开发的世界,企业级项目课程让你达到企业嵌入式应用开发要求.名师在线答疑,解决疑难.科学评测体系,系统评估学习.核心项目实........ 30 门课程 241小时12分钟 824 人学习 学 ...
- 安装完最小化 RHEL/CentOS 7 后需要做的 30 件事情7. 安装 PHP PHP 是用于 web 基础服务的服务器端脚本语言。它也经常被用作通用编程语言。在最小化安装的 CentOS 中安
CentOS 是一个工业标准的 Linux 发行版,是红帽企业版 Linux 的衍生版本.你安装完后马上就可以使用,但是为了更好地使用你的系统,你需要进行一些升级.安装新的软件包.配置特定服务和应用程 ...
- Microsoft Translator:打破语言障碍 拓展全球沟通新机遇
Translator:打破语言障碍 拓展全球沟通新机遇"> 作者:Olivier Fontana, 微软研究院Microsoft Translator产品战略总监 世界越来越小,全球协 ...
- Linux基础与Linux下C语言编程基础
Linux基础 1 Linux命令 如果使用GUI,Linux和Windows没有什么区别.Linux学习应用的一个特点是通过命令行进行使用. 登录Linux后,我们就可以在#或$符后面去输入命令,有 ...
- java基础学习总结一(java语言发展历史、jdk的下载安装以及配置环境变量)
最近一段时间计划复习一下java基础知识,使用的视频课程是尚学堂高淇老师的,上课过程中的心得体会直接总结一下,方便以后复习. 一:计算机语言的发展 1:机器语言,最原始的语言,主要有“01”构成,最早 ...
- 零基础数据分析与挖掘R语言实战课程(R语言)
随着大数据在各行业的落地生根和蓬勃发展,能从数据中挖金子的数据分析人员越来越宝贝,于是很多的程序员都想转行到数据分析, 挖掘技术哪家强?当然是R语言了,R语言的火热程度,从TIOBE上编程语言排名情况 ...
- Java基础笔记(1)----语言基础
变量 变量:是内存中的一块存储空间,是存储数据的基本单元. 使用:先声明,后赋值,在使用. 声明:数据类型 + 变量名 = 值.(例:int a = 5:) 数据类型 分类:如图: 详解: Strin ...
- 零基础学Python--------第2章 Python语言基础
第2章 Python语言基础 2.1 Python语法特点 2.11注释 在Python中,通常包括3种类型的注释,分别是单行注释.多行注释和中文编码声明注释. 1.单行注释 在Python中,使用 ...
- 【转】Linux基础与Linux下C语言编程基础
原文:https://www.cnblogs.com/huyufeng/p/4841232.html ------------------------------------------------- ...
随机推荐
- 洛谷 P3413 【萌数】
敲完这篇题解,我就,我就,我就,嗯,好,就这样吧... 思路分析: 首先我们要知道一个回文串的性质--假如说一个[l-1,r+1]的串是回文的,那么[l,r]一定也是回文的. 所以我们只要记录前一个数 ...
- NOIP提高组2016 D1T2 【天天爱跑步】
码了一个下午加一个晚上吧...... 题目描述: 小c同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是一个养成类游戏,需要玩家每天按时上线,完成 ...
- cocos creator屏幕适配的一些知识点
一. cocos creator 提供的几种适配策略 EXACT_FIT: 整个应用程序在指定区域可见,无需尝试保留原始纵横比.可能会出现失真,应用程序会被拉伸或压缩.也就是说设计分辨率的长和宽不会等 ...
- ubuntu19.10如何设置固定ip
$ip a 看见系统中有两块网卡 lo: ...... ens33: ...... #cd /etc/netplan$ls目录下面有文件01-network-manager-all.yaml $sud ...
- 多测师讲解selenium_alert弹框定位_高级讲师肖sir
from selenium import webdriverfrom time import sleepdrvier=webdriver.Chrome()url=r'F:\dcs\DCS课程安排\se ...
- 自定义常用input表单元素三:纯css实现自定义Switch开关按钮
自定义常用input表单元素的第三篇,自定义一个Switch开关,表面上看是和input没关系,其实这里采用的是checkbox的checked值的切换.同样,采用css伪类和"+" ...
- centos7 shell 安装docker redis mongodb 等
脚本下载: 百度网盘 提取码: wc4r 下载后上传到 服务器 chmod +x docker.sh sh docker.sh
- css和xpath定位补充
定位的网页的地址:https://item.jd.com/100000323510.htmlxpath绝对定位: 1 以"/"开头的是绝对定位 2 /html/body/div[1 ...
- .net 连接数据库实例
web.config配置 <appSettings> <add key="ConnectionString" value="server=.;datab ...
- 【API管理 APIM】APIM中对后端API服务的DNS域名缓存问题
问题描述 在使用API Management来进行API管理时,当我们后端的API DNS IP地址发生改变或者是API的域名发生改变后,通过APIM请求访问的还是是旧的域名或者IP地址,这是因API ...