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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值