目录
一.C++输出与输入
二.缺省参数
1.概念
2.缺省参数分类
(1) 全缺省参数
(2)半缺省参数
三.函数重载
1.概念
2.C++支持函数重载的原理--名字修饰
四.引用
1.概念
2.语法
3.引用的特性
(1)引用在定义时必须初始化
(2)引用时不能改变指向
(3)一个变量可以有多个引用
4.引用的使用场景
(1)做参数
①输出型参数
②对象比较大,减少拷贝,提高效率
(2)做返回值
①正常情况
②返回引用的情况
五.引用和指针的区别
一.C++输出与输入
#include<iostream>
// std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中
using namespace std;
int main()
{
cout<<"Hello world!!!"<<endl;
return 0;
}
- 说明
1. 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件
以及按命名空间使用方法使用std。 2. cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含< iostream >头文件中。 3. <<是流插入运算符,>>是流提取运算符。 4. 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。 C++的输入输出可以 自动识别变量类型。注意:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,规定C++头文件不带.h;旧编译器(vc 6.0)中还支持<iostream.h>格式,后续编译器已不支持,因此推荐使用<iostream>+std的方式。
- 图示
二.缺省参数
1.概念
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。
- 示例
void Func(int a = 0)
{
cout << a << endl;
}
int main()
{
Func(); // 没有传参时,使用参数的默认值
Func(10); // 传参时,使用指定的实参
return 0;
}
2.缺省参数分类
(1) 全缺省参数
void Func(int a = 10, int b = 20, int c = 30)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
(2)半缺省参数
void Func(int a, int b = 10, int c = 20)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
int main()
{
Func(1);
return 0;
}
- 注意
1. 半缺省参数必须从右往左依次来给出,不能间隔着给。
2. 缺省参数不能在函数声明和定义中同时出现。(最好在声明给,如果声明没有给,那定义处也不要给)。
3. 缺省值必须是常量或者全局变量。
4. C语言不支持(编译器不支持)。
三.函数重载
1.概念
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。
- 示例
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;
}
2、参数个数不同
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
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;
}
int main()
{
Add(10, 20);
Add(10.1, 20.2);
f();
f(10);
f(10, 'a');
f('a', 10);
return 0;
}
- 结果显示
2.C++支持函数重载的原理--名字修饰
- 为什么C++支持函数重载,而C语言不支持函数重载呢?
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接
预处理:展开头文件、进行宏替换、条件编译、去掉注释编译:检查语法
汇编:把汇编代码转成二进制机器码
链接:完成文件中各种调用的函数以及库的连接,并将它们一起打包合并形成可执行文件
1. 实际项目通常是由多个头文件和多个源文件构成,而通过C语言阶段学习的编译链接,我们可以知道,【当前a.cpp中调用了b.cpp中定义的Add函数时】,编译后链接前,a.o的目标
文件中没有Add的函数地址,因为Add是在b.cpp中定义的,所以Add的地址在b.o中。那么
怎么办呢?
2. 所以链接阶段就是专门处理这种问题, 链接器看到a.o调用Add,但是没有Add的地址,就会到b.o的符号表中找Add的地址,然后链接到一起。
- 链接的时候面对相关函数,链接接器会使用哪个名字去找呢?
使用C语言编译器:
可以看到在C语言编译器下函数名字的修饰没有发生改变。
使用C++编译器:
在C++编译器下函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中。
- 总结
在不同编译器下函数被修饰后函数名发生了变化,C语言中函数名没有改变而C++中函数名的形式变为【_Z+函数长度+函数名+参数类型首字母(指针的话为Pi)】 .这里可以了解C语言无法支持重载是因为无法区分函数名,而C++通过修饰函数名来进行区分,只要参数类型不同,即视为不同函数,也就支持了重载。
四.引用
1.概念
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"。
2.语法
类型& 引用变量名(对象名) = 引用实体
void TestRef() { int a = 10; int& ra = a;//<====定义引用类型 printf("%p\n", &a); printf("%p\n", &ra); } int main() { TestRef(); return 0; }
注意:引用类型必须和引用实体是同种类型的。
3.引用的特性
(1)引用在定义时必须初始化
void TestRef1()
{
int a = 10;
// int& ra; 该条语句编译时会出错
int& ra = a;
}
(2)引用时不能改变指向
void TestRef2()
{
int a = 10;
int& ra = a;
int b = 2;
ra = b;//产生歧义
}
ra=b这条语句,不是改变了让指向,而是赋值。
(3)一个变量可以有多个引用
void TestRef2()
{
int a = 10;
int& ra = a;
int& rra = ra;
printf("%p %p %p\n", &a, &ra, &rra);
}
4.引用的使用场景
(1)做参数
①输出型参数
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
int main()
{
int a = 1;
int b = 2;
Swap(a, b);
cout << "a==" << a << endl;
cout << "b==" << b << endl;
return 0;
}
②对象比较大,减少拷贝,提高效率
struct A { int a[10000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void TestRefAndValue()
{
A a;
// 以值作为函数参数
size_t begin1 = clock();
for (size_t i = 0; i < 10000; ++i)
TestFunc1(a);
size_t end1 = clock();
// 以引用作为函数参数
size_t begin2 = clock();
for (size_t i = 0; i < 10000; ++i)
TestFunc2(a);
size_t end2 = clock();
// 分别计算两个函数运行结束后的时间
cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
int main()
{
TestRefAndValue();
return 0;
}
(2)做返回值
①正常情况
int func()
{
int a = 0;
return a;
}
int main()
{
int ret = func();
cout << ret << endl;
return 0;
}
- 细节理解
函数调用完成后生命周期结束,栈帧销毁,此时变量a销毁或是复制给另一个临时变量(可能为寄存器或者其他已经开辟好的一块空间。这个临时变量生命周期较长,能将其值复制给ret变量),此时若用a当做返回值,返回的很可能是随机值(根据平台不同来定)。
②返回引用的情况
int& func()
{
int a = 0;
return a;
}
int main()
{
int ret = func();
cout << ret << endl;
return 0;
}
这里返回a的别名,也就是func函数中变量a自身,由于函数调用结束,栈帧销毁,这里返回的具体是什么要看函数销毁后变量a所占的那块空间销毁是否销毁,如果销毁了,那么返回随机值,如果没销毁可能可以拿到变量a本身的值,也可能是类型及大小相同的另一个函数,被调用后所留下的另一个值。
- 总结
返回的变量(如局部变量)出了函数作用域后生命周期结束,不能用引用返回。
全局变量、静态变量、堆上变量等就可以用引用返回
五.引用和指针的区别
1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
2. 引用在定义时必须初始化,指针没有要求。
3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何 一个同类型实体。
4. 没有NULL引用,但有NULL指针。
5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32 位平台下占4个字节)。
6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小。
7. 有多级指针,但是没有多级引用。
8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理。9. 引用比指针使用起来相对更安全。