函数重载是指在同一作用域内使用相同的函数名定义多个函数,这些函数的参数列表(参数的个数或类型)必须不同。函数重载是一种多态的表现形式,常见于面向对象的编程语言如C++和Java。
#include
using namespace std;
int Add(int a, int b) {
return a + b;
}
float Add(float a, int b) {
return a + b;
}
double Add(double a, int b) {
return a + b;
}
int Add(double a, double b) {
return a + b;
}
//函数重载
int main() {
cout << Add(1, 1) << endl;
cout << Add(1.1f, 1) << endl; //1.1f数据类型为float
cout << Add(1.1, 1) << endl;//1.1 默认数据类型为double
cout << Add(1.1, 1.1) << endl;
std::cout << "Hello, World!" << std::endl;
return 0;
}
为什么C++支持函数重载,而C语言不支持函数重载呢?
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。
这里我们以gcc编译器和g++编译器来演示C语言和C++的链接规则(过程好演示一点)。
这里涉及到C语言链接的时候是根据符号表去根据函数名去找相应的函数,当一个文件开始运行时,符号表会收集所有的函数名,这里函数名不加修饰,由于是根据函数名来找函数,所以函数名不能重复,不然不知找哪个!
而C++也是有符号表的,但是这个符号表记录的函数名和C语言不一样,这里的函数名会加以修饰,修饰规则是:==_Z+函数长度
+函数名+类型首字母==。
引用不是新定义一个变量,而是给已存在变量取了一个别名。编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
语法:类型& 引用变量名(对象名) = 引用实体;
int main() {
int a = 1;
int &b = a;
cout << &b << endl << &a << endl;//地址一样
return 0;
}
注意:引用类型必须和引用实体是同种类型的。
引用在定义时必须初始化。
一个变量可以有多个引用,这些引用可以指向同一个实体,也可以指向不同的实体。
引用一旦引用一个实体,就不能再引用其他实体。
int main() {
int a = 1, b = 0;
int& c = a;
int& d = a;
//int& c = b;//报错,一个变量只能引用一个变量
cout << &a << endl << &c << endl << &d << endl;//地址一样
return 0;
}
除了普通的引用外,C++还提供了常引用。所谓常引用,就是一旦一个常引用被初始化指向一个对象后,它就不能被重新指向另一个对象。
引用类型必须和引用实体是同种类型的。但是仅仅是同种类型,还不能保证能够成功引用。如果用一个普通引用类型去引用其对应的非常量类型对象,那么编译器将会报错。我们不可以可以将一个安全的类型(const修饰的类型)交给一个不安全的类型(可被修改的类型)。对于一个非常量引用,它可以引用一个非常量对象,也可以引用一个常量对象。但是对于常量对象,我们不能改变它的值也不能改变它的类型(即权限不能放大,但是权限可以缩小)。
int main() {
//权限不能放大
const int a = 10;
//int &b = a;//报错
const int &b = a;
const int &c = b;
//c = 1;//报错
//权限可以缩小
int d = 20;
const int &e = d;
//可以给常数取别名
const int &f = 10;
int i = 1;
double j = i;
//double &rj = i;
const double &rj = i;//i先int提升为double,提升后会有一个常变量记录这个提升后的i(i的原始值不变),所以必须使用常量来引用
return 0;
}
做参数:引用的一个常见用途就是在函数中作为参数使用。在函数中,我们可以通过引用来传递参数,而不是直接传递参数的值。这样可以避免数据的复制,提高了效率。
void Swap(int &a, int &b) {
int tmp = a;
a = b;
b = tmp;
}
int main() {
int a = 1, b = 3;
cout << a << " " << b << endl;//1 3
Swap(a, b);
cout << a << " " << b << endl;//3 1
return 0;
}
做返回值:与做参数类似,我们也可以使用引用来返回函数的结果。这样可以避免数据的复制,提高了效率。在返回值是大型对象时特别有用。
int &Add(int a, int b) {
static int c = a + b;//只初始化一次
return c;
}
int main() {
int &ret = Add(1, 2);
cout << ret << endl;//3
Add(4, 5);
cout << ret << endl;//3
return 0;
}
ret
的值也是3
?因为在Add
函数里,变量c
是静态变量,第一次调用Add
函数的时候它存在静态区(堆区),第二次调用的时候static int c = a + b;
不会执行,因为静态变量c
只会被初始化一次,那么第二次返回的就是堆区的c
,也就还是3
。在C++中,传值和传引用都可以用来传递参数给函数,但它们在执行效率和内存使用上有一些不同。
传值是通过复制实参的值给形参,在函数内部,形参是实参的一个副本,改变形参的值不会影响实参。这种方式不会避免对大型对象的复制操作,缺点是会占用额外的内存来存储副本。
传引用是通过将实参的内存地址给形参,在函数内部,形参可以直接访问实参的内存地址。这种方式可以直接访问并修改实参,优点是可以避免对大型对象进行昂贵的复制操作。
在执行效率方面:
- 传值对于小型对象来说效率较高,因为复制操作相对较快。
- 传值对于大型对象来说效率较低,因为复制操作需要花费更多的时间和内存。
- 传引用对于小型对象和大型对象来说效率都较高,因为不需要需要使用额外的内存来存储形参。
在内存使用方面:
- 传值需要额外内存来存储副本,如果传递的对象很大,会导致内存占用增加。
- 传引用不需要额外内存来存储副本,但是需要使用指针来访问对象,可能会导致指针错误或者空指针问题。
在实际编程中,选择传值还是传引用取决于具体的情况。如果传递的对象较大或者需要修改对象,使用传引用可以提高效率;如果只是需要传递对象的值而不是修改对象,使用传值可能更加合适。
值和引用的作为函数参数类型的性能比较
#include
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;
}
值和引用的作为返回值类型的性能比较
#include
struct A{ int a[10000]; };
A a;
// 值返回
A TestFunc1() { return a;}
// 引用返回
A& TestFunc2(){ return a;}
void TestReturnByRefOrValue()
{
// 以值作为函数的返回值类型
size_t begin1 = clock();
for (size_t i = 0; i < 100000; ++i)
TestFunc1();
size_t end1 = clock();
// 以引用作为函数的返回值类型
size_t begin2 = clock();
for (size_t i = 0; i < 100000; ++i)
TestFunc2();
size_t end2 = clock();
// 计算两个函数运算完成之后的时间
cout << "TestFunc1 time:" << end1 - begin1 << endl;
cout << "TestFunc2 time:" << end2 - begin2 << endl;
}
int main(){
TestReturnByRefOrValue();//传引用效率更高
return 0;
}
传引用不需要对大型对象进行复制操作。因为在C++中,当一个大型对象被传递给函数时,使用引用参数可以提高参数传递的效率。引用并不产生对象的副本,也就是说,在参数传递时,对象不需要被复制。因此,通过引用,函数可以直接访问并修改实参的内存地址,而不需要像传值那样将实参的值复制给形参,也不需要像传指针那样需要使用额外的内存来存储指针。因此,传引用对于大型对象来说可以提高效率。
在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。但是我们一般都认为引用是没开空间的!
int main()
{
int a = 10;
int& ra = a;
ra = 20;
int* pa = &a;
*pa = 20;
return 0;
}
引用和指针的不同点:
总结起来,指针和引用虽然都是C++中重要的概念,但在使用上、功能上有明显的差异。
内联函数是指用inline关键字修饰的函数,或者在类体内定义的成员函数。
内联函数在编译时,会被嵌入到每一个调用处,而不是在调用时发生控制转移。这使得内联函数可以消除函数调用时的时间开销,通常用于频繁执行的函数。
如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用。
如下图:
需要注意的是,递归函数不能被定义为内联函数。此外,内联函数一般适合于不存在复杂的结构(如while和switch等)且只有1~5条语句的小函数。如果一个内联函数有多个执行路径(如在if-else结构中),编译器可能会把它视为普通函数。
另外,使用内联函数时要注意:内联函数只能先定义后使用;不能对内联函数进行异常的接口声明。即内联函数不能声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
// F.h
#include
using namespace std;
inline void f(int i);//声明
// F.cpp
#include "F.h"
void f(int i)//定义
{
cout << i << endl;
}
// main.cpp
#include "F.h"
int main()
{
f(10);
return 0;
}
// 链接错误:main.obj : error LNK2019: 无法解析的外部符号 "void __cdecl f(int)" (?f@@YAXH@Z),该符号在函数 _main 中被引用
在C++中,
auto
关键字用于自动类型推断,即让编译器根据变量的使用上下文自动确定其类型。auto
在C++11及其后的版本中引入,用于简化代码并减少手动指定变量类型的需求。使用
auto
关键字可以使代码更简洁,并可以减少因手动指定类型而产生的错误。例如,假设有一个复杂的表达式或变量,手动指定其类型可能会很麻烦或容易出错。使用auto
可以让编译器自动推断出正确的类型。
auto可以自动识别类型。如下:
#include
int main() {
int a = 1;
auto b = &a;
auto c = a;
auto d = b;
std::cout << typeid(a).name() << std::endl << typeid(b).name() << std::endl << typeid(c).name() << std::endl << typeid(d).name() << std::endl;// i Pi i Pi
//i表示int ,Pi表示Pointer int,也就是int*
return 0;
}
但是auto不是用在这么简单的类型上,在后面我们学的越来越多,越来越深后,类型的长度也会有更长的。比如迭代器。
#include
#include
it
的类型是std::map::iterator
,这里我们用auto关键字就会使得代码简单很多。#include
#include
注意:使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。
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;
}
在同一行定义多个变量
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
void TestAuto()
{
auto a = 1, b = 2;
//auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}
auto不能作为函数参数
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{
//考虑一个问题,如果这个函数定义了,但是没调用,那么auto推导的类型是什么?所以肯定不行吧
}
auto不能用来声明数组
void TestAuto() {
int a[] = {1, 2, 3};
//auto b[] = {4,5,6};//auto不能用来声明数组
}
为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法
auto在实际中最常见的优势用法就是跟以后会讲到的C++11提供的新式for循环(基于范围的for循环),还有lambda表达式等进行配合使用。
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
即for(用于迭代的变量:被迭代的范围){}
。
//传统for
void TestFor1() {
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;
}
//新型for
void TestFor2() {
int array[] = {1, 2, 3, 4, 5};
for (auto &e: array)
e *= 2;
for (auto e: array)
cout << e << " ";
return 0;
}
注意:与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。
for循环迭代的范围必须是确定的
对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循迭代的范围。
注意:以下代码就有问题,因为for的范围不确定
void TestFor(int array[]) {
for (auto &e: array)
cout << e << endl;
}
迭代的对象要实现++和==的操作,我们看到这里for循环里使用auto &e
,在范围for循环中使用auto&
可以引用容器或数组中的元素,并对这些元素进行下一步操作。
简而言之nullptr
就是用来解决NULL
的在某些方面的不好。
NULL
有可能被定义为字面常量0
,也可能被定义为无类型指针(void*)0
的常量。
void f(int) {
cout << "f(int)" << endl;
}
void f(int *) {
cout << "f(int*)" << endl;
}
int main() {
f(0);//输出f(int)
f(NULL);//输出f(int)
f((int *) NULL);输出f(int*)
return 0;
}
但是nullptr
就纯纯是无类型指针(void*)0
的常量。
void f(int) {
cout << "f(int)" << endl;
}
void f(int *) {
cout << "f(int*)" << endl;
}
int main() {
f(0);//输出f(int)
f(nullptr);//输出f(int*)
f((int *) NULL);输出f(int*)
return 0;
}
注意:
在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
OKOK,C++入门篇2就到这里。如果你对Linux和C++也感兴趣的话,可以看看我的主页哦。下面是我的github主页,里面记录了我的学习代码和leetcode的一些题的题解,有兴趣的可以看看。
Xpccccc的github主页