c++-函数
函数
可以为函数多次声明,但是尽量不要这么做(后续的声明只能为之前哪些没有默认参数的值添加默认值)
默认参数
int b=2;
void func(int a=1,int c=b+2)
{
}
注意:C++中在给定的作用域中只能指定一次默认参数
且编译器使用的是当前作用域中的默认参数
// 下面是在一个文件下同时进行声明和定义
// 下面通常来讲是错误的
void func(int a=1);
int main()
{
func();
return 0;
}
void func(int a=1) // 不能同时指定默认参数
{
cout<<a<<endl;
}
第二个例子
这个例子就没有错误
/*test.c*/
void func(int a=1) // 定义的作用域在test.c文件中
{
/*code*/
cout<<a<<endl;
}
/*main.c*/
extern void func(int a=2); // 声明的作用域在main.c文件中
int main()
{
func(); // a结果是2 不是1,编译器使用的是当前作用域中的默认参数
return 0;
}
函数重载
函数功能类似,实现细节不同
函数名可以相同,参数列表不同(参数个数不同 或 参数类型不同 或 参数循序不同)就可以
编译器会自动根据实参类型进行调用
函数名必须相同,参数的返回值可以相同,也可以不相同
函数重载,本质上是不同的函数,占用不同的内存,入口地址不一样
注意:参数名不同不可以;参数名相同的情况下有缺省和无缺省 的重载不可以
注意:函数重载时的二义性问题(多个参数和类型转换的二义性),见文档
传递方式
内置数据类型,可以值传递、指针传递,引用传递没有必要(引用传递一般就是值传递)
数组必须是指针传递
结构体和实例对象可以值传递也可以指针传递
函数内的局部参数、形参,只有在栈上分配内存,也就是在函数声明和定义的时候参数没有被创建
对于返回值是结构体和类对象时(值传递),为了防止局部对象被销毁,也为了防止通过返回值修改原来的局部对象,编译器并不会返回这个对象,而是根据这个对象先创建一个临时对象(匿名对象)以拷贝的方式进行,然后再将这个临时对象返回
值传递
相当于深拷贝
指针(地址)传递
相当于浅拷贝
引用传递
c语言中没有引用传递
相当于别名
引用必须在定义的同时进行初始化,且不能在改变,即不能在引用其他数据
dataType &name=data;
对于指针
https://blog.csdn.net/llm_hao/article/details/108432323
typedef int * PINT;
PINT p1;
PINT &p2=p1; // p2是p1的别名
引用传递与const
对于自定义类型的参数(非内部类型)
/*
在函数体内会产生自定义类型的临时对象用于赋值形参
相当于深拷贝,这个临时(拷贝)对象的构造、复制、机构过程都消耗事件
**需要注意的是,对于内部类型,int|float等形参,不存在构造、析构过程**
*/
void func(MyClass item){} // 这个方法不会修改源数据
// 可以改写为 引用传递的方式
void func(MyClass &item){} // 但是这个方法会修改源数据
// 为了不修改源数据,又不消耗额外的内存
void func(const MyClass &item)
指针引用
注意事项: 地址传递的地址(地址)依然是副本(深拷贝、地址传递)
https://blog.csdn.net/llm_hao/article/details/108432323
https://blog.csdn.net/magicdoubi/article/details/98171260
通过地址(指针)传递可以修改指向的内容,但是指针本身是值传递
(当把一个指针作为参数传递给一个函数或方法时,其实是把指针的副本copy传递给了函数,即把指针的值本身传递到ptr,因此当在函数或方法内部修改指针(注意,不是修改指针所指向的值)时,其实修改的是函数内部指针的副本,而非外部的指针本身)
如果不是指针引用:指针的指向改变并不影响原指针的指向,指针指向的值的改变可以影响原值
进阶例子,看算法与数据结构dsa-单链表 - 重要
typedef struct LNode
{
int data;
struct LNode *next; // 用于指向下一个节点
}LNode,*LinkList;
// typedef struct LNode NODE;
// typedef struct LNode *LinkList;
// 下面其实是一样的,不过看起来更优雅
// LNODE * 表示一个节点,一个指向一个节点的指针
// LinkList 表示一个链表
/*
L1 相当是L的别名 而且是指针别名
修改L1的指向,就是修改L的指向
如果bool InitList(LinkList L1){}这样
一开始形参(局部变量)L1和L指向的是同一块内存,但是L1=NULL后就指向的不是同一块内存了就是错误的
*/
bool InitList(LinkList &L1) // 引用传递
{
L1=NULL; //struct LNode* ,这个必须是指针引用,否则,就不对
return true;
}
int main()
{
LinkList L; // 相当于指针引用
InitList(L);
return 0;
}
引用与函数返回值
返回的是return 数据的别名
注意: 将引用作为函数返回值的时候,不能将局部数据的引用,因为局部数据存在栈区,函数调用结束后会被销毁,有可能下次使用数据的时候就不存在了
int &func(int &r)
{
r+=10;
return r;
}
int main()
{
int var=1;
cout<<var<<endl; //1
int var2=func(var); //11
cout<<var<<endl;
cout<<&var<<" "<<&var2<<" "<<&func(var)<<endl;
cout<<var<<endl; // 21
int &var3=func(var);
cout<<var<<endl; //32
cout<<&var3<<endl;
// &var == &func(var)==&var3
// &var != &var2
return 0;
}
例子2
不能将函数返回值直接对引用变量进行初始化
float fn1(float r){
temp = r*r*3.14;
return temp;
}
// float &b = fn1(5.0); // ERROR
// 逻辑是首先拷贝 temp 的值给临时变量。返回到主函数后,用临时变量来初始化引用变量 b
// 使得b成为该临时变量到的别名。但是临时变量的作用域短暂(仅仅是一句表达式)会使得 b
// 有无效的风险。所以建议使用
float x = fn1(5.0);
float &b = x;
引用与指针
相同
引用就是对指针的封装,底层还是基于指针
int a=10;
int &a2=a;
cout<<&a2<<" "<<&a<<endl;
// 在编译器下会被编译成
int a=10;
int *a2=&a;
cout<<a2<<" "<<&a<<endl;
区别
引用必须在定义时进行初始化,且不能被修改
指针可以被修改
数组必须是指针传递,不能使用引用传递(数组型字符串只能是指针,不能是引用)
int &arr2[4]=arr1 没有这种写法
结构体和实例对象可以值传递也可以指针传递
引用++ 是数值+1
指针++ 是地址+1,指向下一个元素
引用与临时数据 - 还要看
见文档
引用不能指代临时数据
只有加上const
还是正确的
int m=10,n=20;
// 下面都属于临时数据变量,不能被引用所指代,都是错的
int &a=m+n;
int &a=1+2;
int &a=2*3;
int &a=m+1;
int &a=func();
void func(int &a) {}
func(m+n);
func(1+2);
func(m+1);
// 下面是正确的
const int &a=m+n;
const int &a=1+2;
const int &a=2*3;
const int &a=m+1;
const int &a=func();
void func(const int &a) {}
func(m+n);
func(1+2);
func(m+1);
引用与数组
对数组的引用需要加上数组的大小
int a[3] = {1, 2, 3};
int (&b) [3] = a;
左值引用 / 右值引用 / const
https://blog.csdn.net/m0_59938453/article/details/125858335 – 没看完
https://blog.csdn.net/weixin_38587349/article/details/80891437 – 没看完
https://blog.csdn.net/yue152152/article/details/127214251 – 没看完
见 ‘进阶其他’ / ‘c++ 面向对象’ 移动拷贝构造
左值:指向内存位置的表达式, 叫做左值表达式(存储在内存中,有明确存储地址(可寻址)的数据)
右值:内存中某些地址的数值(可以提供数据值的数据(不一定可以寻址,例如存储于寄存器中的数据))
左右值:看能不能取地址
左值引用:普通写法
右值引用:对右值取别名(右值包括:常量、表达式、传值函数的返回值)
右值引用:主要目的是提高程序运行的效率,有些对象在复制时需要进行深复制,深复制往往非常耗时,合理使用右值引用可以避免不必要的深复制
// 左值引用
int num = 10;
int &b = num; // 正确
int &c = 10; // 错误
int num = 10;
const int &b = num; // 正确
const int &c = 10; // 正确
// 右值引用
int num = 10;
//int && a = num; // 错误,右值引用不能初始化为左值
int && a = 10; // 正确
// 以下几个是对上面右值的右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);
注意:
右值引用引用右值,会使右值被存储到特定的位置。
也就是说,右值引用变量其实是左值,可以对它取地址和赋值(const右值引用变量可以取地址但不可以赋值,因为 const 在起作用)。
当然,取地址是指取变量空间的地址(右值是不能取地址的)。
double&& rr2 = x + y;
&rr2;
rr2 = 9.4;
// 右值引用 rr2 引用右值 x + y 后,该表达式的返回值被存储到特定的位置,不能取表达式返回值 x + y 的地址,但是可以取 rr2 的地址,也可以修改 rr2 。
const double&& rr4 = x + y;
&rr4;
// 可以对 rr4 取地址,但不能修改 rr4,即写成rr4 = 5.3;会编译报错。
const 与函数
https://blog.csdn.net/u013377887/article/details/108887633
https://blog.csdn.net/cuizhiyi2008/article/details/102784754
const与形参
见上面的引用传递
// 为了不修改源数据,又不消耗额外的内存
void func(const MyClass &item)
const修饰函数(返回值)
修饰的是函数的返回值
返回值的内容不能被修改,只能赋值给同类型的const的内容
const 只修饰指针类型的返回值,值传递方式的返回值没有任何意义
const dataType func();
const var=func(); // 这种是对的
// 下面是错的
// const var;
// var=func()
const 成员函数
在成员函数声明和定义的后面加入关键字const,表示const成员函数可以使用类中的所有成员变量,但是不能修改他们的值
见’c+±面向对象’
inline 内联函数
函数调用机制
函数调用是有时间和空间开销的,程序在执行一个函数之前需要做一些准备工作,要将实参、函数变量、返回值地址以及若干寄存器都压入栈中,然后再才能执行函数体中的代码,函数体中的代码执行完毕后还要清理现场,将之前压入栈中的数据都出栈,才能接着执行函数调用位置以后的代码
如果函数体代码比较多,需要较长的执行事件,那么函数调用机制占用的时间可以忽略
如果函数只有一两句语句,那么大部分的时间都会花费在函数调用机制上,这种事件开销就不容忽略
内联函数
为了消除上述函数调用的时空开销,C++提供了一种提高效率的方法,即在编译时将函数调用处用函数体替换,类似C语言中的宏展开
内联函数:在函数调用处直接嵌入函数体的函数称为内联函数
一般将非常短小的函数声明为内联函数
由于内联函数比较短小,通常的做法是省略函数原型,将整个函数定义(包括函数头和函数体)放在本应该提供函数原型的地方
内联函数的缺点:
编译后的程序会存在多份相同的函数拷贝,如果被声明为内联函数的函数以非常大,那么编译后的程序体积也会变得很大,所以,一般只将那些短小的、频繁调用的函数声明为内联函数
(inline
声明只是程序员对编译器提出的一个建议,而不是强制性的,并非一经指定为inline
编译器就必须这样做,编译器有自己的判断能力,会根据具体情况决定是否这样做)
内联函数的使用
内联函数能提高函数的执行效率,但是是以代码拷贝为代价,仅仅为了省去函数调用的开销,从而提高程序的执行效率(开销:指的是参数的压栈、跳转、退栈和返回操作)
一方面,如果执行函数体内的代码时间比函数调用的开销大的多,那么inline
效率收益会很小
一方面,每一处内联函数的调用都要拷贝代码,使程序的总代码量增大,消耗更多的内存空间
不宜使用:函数体内代码比较长,使用内联将导致执行代码拷贝过大
不宜使用:函数体出现循环或者其他复杂的控制结构,那么执行函数体内代码的时间将函数调用的开销大的多
例子
#include <iostream>
using namespace std;
inline void swap(int *a,int *b)
{
int temp;
temp=*a;
*a=*b;
*b=temp;
}
int main()
{
int m=1,n=2;
cout<<m<<" "<<n<<endl;
swap(&m,&n);
cout<<m<<" "<<n<<endl;
return 0;
}
/* 编译器会将swap()调用函数的代码替换成 */
int temp;
temp=*(&m);
*(&m)=*(&n);
*(&n)=temp;
内联函数使用
inline
关键字应该加在函数定义的前面,(在函数声明前是无效的)
内联函数再编译拷贝后就消失了
对于多文件的inline函数定义在编译和连接时有问题(也就是通常不需要进行函数原型,而是声明和定义一块写)因此多文件中,直接将
inline
函数定义在.h
文件中,且不需要内联函数的声明
下面的方法是错误的,对应上面的第三条‘有问题’
/* main.c*/
#include "func.c"
void func(); // 函数声明 extern void func()
int main()
{
func(); //函数调用
return 0;
}
/* func.c */
inline void func()
{
cout<<"inline func"<<endl;
}
类中内联函数
在类体中定义的成员函数会自动称为内联函数
(我的理解:一般在类实现中,不要考虑内联函数,就使用 类内声明,类外定义)
(要想使用内联函数,就直接将函数定义在类的内部)
lambda表达式 - 匿名函数
仍然是一个函数,只是没有名字
[外部变量访问方式说明符](参数表)->返回值类型
{
// code block
}
1. 外部变量访问说明符:
= 或 &
表示{}语句块中用到的,定义在{}外面的变量在{}中是否被允许改变
= 表示不允许改变
& 表示允许改变
[] 表示不捕获任何外部变量
[=] 以值传递的方式捕获所有外部变量
[&] 表示以引用方式捕获所有外部变量
[var1,var2] 表示仅捕获指定的外部变量
例子
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
int a[4]={1,2,3,4};
int total=0;
// [&]表明lambda表达式中用到的外部变量total是引用传递
for_each(a,a+4,[&](int &x){
total+=x;
x*=2;
});
cout<<total<<endl;
for_each(a,a+4,[=](int x){
cout<<x<<endl;
});
return 0;
}
例子2
// 引用捕获
int x=10;
cout<<x<<endl; // 10
auto func=[&x](){
x=20;
cout<<x<<endl;
};
func(); // 20
cout<<x<<endl; // 20
// 不捕获
auto func2=[](int x,int y){
cout<<x+y<<endl;
};
func2(2,3); // 5
// 无参数列表
auto func3=[]{
cout<<"lambda func"<<endl;
};
func3(); // lambda func
// 值捕获与引用捕获
int b=100;
auto func4=[=](){
cout<<x<<" "<<b<<endl;
};
auto func5=[&](){
cout<<x<<" "<<b<<endl;
};
b=200;
func4(); // 20 100
func5(); // 20 200
函数对象
见’C++ - 模板’ ,‘C++ - 运算符重载’
与类相关
例子 - 纯C++思考
#include <iostream>
using namespace std;
class A
{
private:
int *arrA;
public:
A(/* args */);
void funcA(int *arr)
{
cout<<"int funcA"<<endl;
// arrA=arr;
arrA=new int[3];
memcpy(arrA,arr,sizeof(int)*3);
cout<<arrA<<endl;
cout<<arr<<endl;
for (int i=0;i<3;i++)
{
arrA[i]++;
}
cout<<endl;
}
};
A::A(/* args */)
{
}
class B
{
private:
A *objA;
int *arr;
public:
B(/* args */);
void funcB(){
arr=new int[3];
arr[0]=1;
arr[1]=2;
arr[2]=3;
cout<<"in funcB"<<endl;
cout<<arr<<endl;
printArr(arr);
objA->funcA(arr);
printArr(arr);
}
void printArr(int *arr)
{
for (int i=0;i<3;i++)
{
cout<<arr[i];
}
cout<<endl;
}
};
B::B(/* args */)
{
objA=new A();
}
int main()
{
A objA;
B objB;
objB.funcB();
return 0;
}
// 输出结果
in funcB
0xf71510
123
int funcA
0xf71550
0xf71510
123