建议先看完上篇:[C++初阶]初识C++(一)—————命名空间和缺省函数-CSDN博客
本篇部分代码和文案来源:百度文库,知乎,比特就业课
1.函数重载
1.1 函数重载概念
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这 些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型 不同的问题。
#include<iostream>
using namespace std;
//参数类型不同
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;
}
#include<iostream>
using namespace std;
//参数个数不同
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
#include<iostream>
using namespace std;
//参数类型顺序不同
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;
}
我们来运行一下
#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;
}
// 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(1, 2);
Add(1.1, 1.2);
f();
f(1);
f(1, 'a');
f('a', 1);
return 0;
}
结果:
那么这时候问题来了,为什么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 的地址,然后链接到一起 。3. 那么链接时,面对Add函数,链接接器会使用哪个名字去找呢?这里每个编译器都有自己的函数名修饰规则。
#include<iostream>
using namespace std;
int sub(int left, int right)
{
cout << "int sub(int left, int right)" << endl;
return left - right;
}
double sub(double left, double right)
{
cout << "double sub(double left, double right)" << endl;
return left - right;
}
int main()
{
sub(1, 2);
sub(1.1, 1.2);
return 0;
}
我们对其反汇编,我们会发现这两个sub的地址不同
int Add(int a,int b)
{
return a+b;
}
void func(int a, double b, int* p)
{}
int main()
{
Add(1,2);
func(1,2,0);
return 0;
}
如果我们采用C语言编译器来编译,我们会发现什么呢?
-----------图片来源 比特就业课 (侵权必删)
我们发现在linux下,采用gcc编译完成后,函数名字的修饰没有发生改变。
但是如果我们采用g++编译呢?
2.引用
2.1引用的定义
int a=10;
int& b=a;
我们来测试一下:
#include<iostream>
using namespace std;
int main()
{
int a = 10;
int& b = a;
cout << a << endl;
cout << b << endl;
return 0;
}
运行结果:
2.2引用的特性
1. 引用在定义时必须初始化2. 一个变量可以有多个引用3. 引用一旦引用一个实体,再不能引用其他实体
void TestRef()
{
int a = 10;
// int& ra; // 该条语句编译时会出错
int& ra = a;
int& rra = a;
printf("%p %p %p\n", &a, &ra, &rra);
}
下面这个也可以
int a = 0;
int& b = a;
int& c = b;
int& d = c;
我们来看一下这段代码,让我们思考一下这个是y变成z的别名呢?
还在y赋值为z呢?
int x = 10;
int z = 20;
int& y = x;
y=z;
我们可以通过监视得到y还是x的地址,所以我们得到这样其实就是把z赋值给x,所以我们呢可以得到特性3.
2.3常引用
void TestConstRef()
{
const int a = 10;
//int& ra = a; // 该语句编译时会出错,a为常量
const int& ra = a;
// int& b = 10; // 该语句编译时会出错,b为常量
const int& b = 10;
double d = 12.34;
//int& rd = d; // 该语句编译时会出错,类型不同
const int& rd = d;
2.4const修饰引用
现在我们给出这样的代码,思考可行性:
const int n = 0;
int& m = n;
int n = 0;
const int& m = 0;
这两段代码完全相反,一个是权力的放大,一个是权力的减小,但是哪个才能运行呢?
答案是第二个
第一个n是只读的,那么m是n的别名,所以不能修改n,也只能是只读的,这里的m的权力放大了,我们因为n是只读的,用m操作n是不行的
第二个是m是只读的,m只对n进行只读这个操作是可以的,所以这样是对的。
结论:权力的放大时不允许的,权力的减小时可行的
2.5使用场景
1. 做参数
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
我们可以通过引用来实现swap函数,这个相对于指针可能更好理解
2.做返回值
int& Count()
{
static int n = 0;
n++;
// ...
return n;
}
2.6引用与指针的差别
1. 引用概念上定义一个变量的别名,指针存储一个变量地址。2. 引用在定义时必须初始化,指针没有要求3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体4. 没有NULL引用,但有NULL指针5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小7. 有多级指针,但是没有多级引用8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理9. 引用比指针使用起来相对更安全
3.内联函数
1.概念
2 .特性
1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰。
4.auto关键字
4.1 auto简介
在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但遗憾的是一直没有人去使用它。C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一 个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
int TestAuto()
{
return 10;
}
int main()
{
int a = 10;
auto b = a;
auto c = 'a';
auto d = TestAuto();
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
//auto e; 无法通过编译,使用auto定义变量时必须对其进行初始化
return 0;
}
注意:使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型 。
4.2auto的使用细则
1. auto与指针和引用结合起来使用
用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时加&
int main()
{
int x = 10;
auto a = &x;
auto* b = &x;
auto& c = x;
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
*a = 20;
*b = 30;
c = 40;
return 0;
}
2. 在同一行定义多个变量
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量
auto a = 1, b = 2;
auto c = 3, d = 4.0; // 该行会编译失败,因为c和d的初始化表达式类型不同,c为实数,d为浮点数
4.3 auto不能推导的场景
1. auto不能作为函数的参数
2. auto不能直接用来声明数组
3. 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法
4. auto在实际中最常见的优势用法就是跟C++11提供的新式for循环,还有 lambda表达式等进行配合使用
5.基于范围的for循环
1.范围for语法
在C++98中如果要遍历一个数组,可以这样进行:
void For() { int array[] = { 1, 2, 3, 4, 5 }; for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i) array[i] *= 2; for (int* p = array; p < array + sizeof(array)/ sizeof(array[0]); ++p) cout << *p << endl; }
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因 此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围
5.2 范围for的使用条件
1. for循环迭代的范围必须是确定的对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围2. 迭代的对象要实现++和==的操作。
6.指针空值nullptr
6.1 C++98中的指针空值
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
void f(int)
{
cout<<"f(int)"<<endl;
}
void f(int*)
{
cout<<"f(int*)"<<endl;
}
int main()
{
f(0);
f(NULL);
f((int*)NULL);
return 0;
}
注意:1. 在使用 nullptr 表示指针空值时,不需要包含头文件,因为 nullptr 是 C++11 作为新关键字引入的 。2. 在 C++11 中, sizeof(nullptr) 与 sizeof((void*)0) 所占的字节数相同。3. 为了提高代码的健壮性,示指针空值时建议最好使用 nullptr 。
初识C++也圆满完成了,下一篇我们将进行对类的学习。