C++ Primer 笔记+习题解答(六)

今天是第六篇笔记,主要内容是函数。现在的节奏基本就是一天读书一天笔记总结。

若有错误 请指正 谢谢

0.引言:

 1.函数:

是一个命名的代码快,通过调用函数可以执行相应的代码。函数通常会返回一个结果。

 2.构成:

返回类型 ,函数名 ,形参列表 ,函数体。

 3.调用:

通过调用运算符(())也就是一对圆括号。调用运算符作用于一个表达式,表达式通常是函数名或者指向函数的指针。圆括号内放实参表,用于初始化对应的形参。看清楚,是初始化,就是我们前面所说的变量初始化,所以理解这个后面就很省力了。

 4.流程:

1.用实参初始化形参列表。2.将控制权从主调函数移交给被调函数,也就是说主调函数的执行被中断,被掉函数开始执行,当被调函数执行完毕,控制权移交回主调函数。

 5.return 语句:

若有值的话,返回值并且结束被调用函数的执行。没有值直接结束函数的执行。

 6.形参实参:

存在一一对应的关系。形参一定要被初始化,但是不规定初始化的顺序,也就是前面所说的求值顺序。

 7.形参列表:

可以为空,但是不可以省略。空形参表有两种形式:

<span style="color:#330099;">void fun();
void fun(void);</span>

 8.返回类型:

1.void类型,比较特殊的一种。2.返回类型不能是数组,函数。但是可以返回指向数组的指针和指向函数的指针。

 9.局部对象:

名字有作用域,对象有生命周期。

名字作用域是程序文本的一部分,名字在作用域中可见。

对象生命周期死程序执行过程中对象存在的一段时间。

函数体是一个语句块,同时存在一个作用域。在形参中或者域函数体内部定义的变量统称为局部变量(local variable)。他们仅在作用域中可见,同时还会隐藏域外的同名对象。通常来说声明周期是从定义处到块末尾,也就是说对象的声明周期取决于它的定义方式。

自动对象:控制路径经过此变量定义的时候创建,定义块的末尾进行销毁,我们把只存在于快执行期间的变量称为自动对象。若定义的变量有初始值,那么用此值进行初始化,否则执行默认初始化,也就是说可能是垃圾值。

局部静态对象(local static variable):生命周期贯穿于函数调用以及调用结束后的时间。也就是说函数执行完毕后对此值是不影响的。静态局部对象是默认初始化为0的,也就是执行值初始化。

 10.函数声明:

函数可以多次声明,但只能定义一次。类似于普通变量,名字要在使用之前声明。通常把声明放在头文件中。因为声明中并不用到形参,所以形参是可以省略的。

函数声明三要素:返回类型,函数名,形参表。描述了函数接口,也称为函数原型(function prototype)

 11.分离式编译:

可以单独编译文件。当修改一个文件的时候,只要单独编译修改过的文件就可以,不需要动全身。

1.参数传递:

  0.引子:

函数每次调用的时候都会创建它的形参,并用传入它的实参对它进行初始化,初始化的流程同变量的初始化完全一致。

一般有两种参数交互方式:pass by value and pass by reference .也就是常见的传值和传引用。

 1.传值:

司空见惯的方式,不赘述。这个地方我们把传地址的方式也归结到穿值的方式。其实这样的做法才是透彻理解的行为。我一开始是区分的,后来有一天我遇到了二叉树的创建问题,结合今天的理解,才算是明悟。

指针形参:在函数体内对指针自身进行修改是不影响源指针的,这个地方虽然是指针,当时当你对指针进行修改的时候,就是传值传递,传的是一份拷贝。只有当你在对指针进行解引用操作,修改才会涉及变量自身。

示例:

<span style="font-size:18px;color:#330099;">void func(int *p){
  p=nullptr;
  *p=0;
}
int i=10,*ptr=&i;
func(ptr)</span>
这个地方我就是想强调对ptr的操作就是传值的方式,也就是说你在体内无论如何修改p,都不会影响ptr,因为这就是指针间的传值拷贝。只有指针涉及到解引用的操作的时候才会影响i.如果你还没看懂我的意思,那么你可以看一下为什么二叉树创建会用二级指针或者是指针的引用。因为在二叉树的创建里涉及到指针的自身操作,如果是普通的传值,在里面的操作是不会影响实际参数的。所以你的创建肯定是失败的。这就是为什么把这个操作归结到传值的原因。

  2.传引用:

也是司空见惯的方式。刚刚上面我提到指针的引用,应该是归结到引用这里的。当引用是形参的时候,这个时候和形参进行交互实际上就是别名的操作了,对别名的操作统统会在实参上得到反馈。而最大的好处就是提高效率,因为避免了拷贝的行为。

 3.返回多个值:

不要被这句话误导了,函数还是只有一个返回值。这个地方的返回多个值的意思是把函数体内的信息带出来,因为函数体内的东西是有声明周期和作用域的。

如何把函数内的信息带出来呢?

两个方式:1.自己定义一个数据类型,比如类,你可以返回一个类。2.用引用传递一个额外参数,通过这个行为,我们都可以让void函数返回一个值,只是通过的渠道不通而已。

 4.const 形参和实参:

这个地方有必要理下顶层const和底层const。

当用实参初始化const形参的时候,其中的顶层const是可以被忽略的。也就是说我们可以用常量或者非常量初始形参。

示例:

<span style="font-size:18px;color:#330099;">int var=10;
const int cvar=var;//顶层const。
const int ccvar=10;</span>
这个地方只是举了初始的例子,函数内是一样的。我们可以看到,虽然类型不同,但是初始化完全没我问题。

甚至忽略顶层const还会导致一个重载函数的错误。

示例:

<span style="font-size:18px;color:#330099;">void fun(int i);
void fun(const int i);
//这是两个函数嘛 ?</span>
实际上编译器会认为这是一个函数,因为他们两的形参是可以互通的,而重载的区分是通过形参的数量和类型区分的。

 5.指针或引用形参与const:

我们可以使用非常量初始一个底层const对象,但是反过来是不可以接受的。同时普通引用必须用同类型进行初始化。

示例:

<span style="font-size:18px;color:#330099;">int i=10;
const int &ref=i;
const int *p=&i;
int *ptr=p;//错。
int &re=10;//错误。
int &r=ref;//错误。 
</span>
同样的,在函数里初始化是同样的要求。

 6.数组形参:

数组的两个特殊性质:1.不能拷贝。2.使用其时会转换为指针。

不能拷贝要求我们不能进行传值调用,转换告诉我们相当于传递指针。

考虑下面三个声明:

<span style="font-size:18px;color:#330099;">void print(const int*);
void print(const int[]);
void print(const int[10]);</span>
算是重载嘛?不算。因为他们都是一个类型:const int*.

因为数组以指针传递的方式传递给函数,但是函数是当成指针理解,并不知道数组的具体大小,为了防止越界行为发生,我们提供了三种改进措施。

  1.显式的提供一个委外参数表示数组大小。

示例:

<span style="font-size:18px;color:#330099;">int arr[10]={0};
void print(const int[],int size);</span>
最简单的方式,我们直接提供数组大小的信息就可以了。

  2.使用标记指定的数组长度,要求数组本身拥有结束字符。最常见的就是c-style 字符串了。自动空字符'\o'。

示例:

<span 
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值