c++ primer 第六章总结
1.传值与传引用
形参与实参:
(1)形参一定会被初始化
(2)形参名可选,如果没有形参名那么我们就不能使用该形参,所以一般都会有形参名。出现未命名的形参,一般就是说我们在该函数中用不到它,但还是必须给他提供实参。
传值与传引用
传值:
(1)发生值的拷贝
(2)形参与实参是两个不同的东西
(3)对形参的操作不会影响实参
传引用
(1)传引用是它对应实参的一个别名
(2)对它操作就是在对实参操作
该注意的几点:
(1)使用传引用来避免拷贝。如果实参很大或者根本就不支持拷贝,只能用传引用。
(2)函数不需要改变引用参数的值就用const来修饰。尽可能使用const
2.管理指针形参
(1)使用标记指定数组长度。如:c语言用’\0’来表示一个字符数组的结束
void print(const char *str)
{
if(str) //指针非空
{
while(*str)
cout << *str++ << " " ;
cout << endl ;
}
}
(2)使用标准库规范
传递头指针与尾后元素的指针(既然是尾后指针,那么就不能解引用)
#include<iostream>
using namespace std;
void print(const int *beg,const int *end)
{
while(beg != end)
{
cout << *beg++ << endl ;
}
}
int main(void)
{
int a[20]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};
print(begin(a),end(a)); //调用形式
}
(3)传递一个表明数组大小的形参。即 void print(const int a[],int size); //size 表示数组有size个元素
3.可变参
想象一个场景。我要实现一个功能相同的函数,但是我每次要给他传的参数不相同。那么就会用到可变参了!
(1)initializer_list 标准库类型。要求实参类型相同 ,常用操作与vector相同,但有一点不同的就是 initializer_list 中的值是常量,不可改变。其实就相当于用const修饰了一下。
#include<iostream>
#include<initializer_list>
using namespace std;
void err_msg(initializer_list<string> list1,int a = 0) //可变参
{
cout << "a== " << a << endl ;
for(auto i=list1.begin();i != list1.end();++i)
cout << *i << " " ;
cout << endl ;
}
int sum(initializer_list<int> const& list2)
{
int sum = 0;
for(auto i: list2)
sum+=i;
return sum;
}
int main(void)
{
err_msg({"liu","sheng","xi"});
err_msg({"rnejn","nbrm"});
err_msg({});
cout << "sum == " << sum({1,2,3,4,5,6,7,8,9}) << endl ;
}
运行截图;
(2)如果实参类型不同->>特殊的函数:可变参数模板
(3)省略符形参
注意事项:
(1)只用于与C函数交互的接口
(2)大多数类类型的对象在传递的过程中都无法正确拷贝
(3)省略符形参只能出现在形参列表的最后一个位置
有下列两种形式:
void foo ( parm_list , …);
void foo ( … );
第一种形式为特定数目的形参提供了声明。在这种情况下,当函数被调用时,对于与显示声明的形参相对应的实参进行类型检查,而对于与省略符对应的实参则暂停类型检查。在第一种形式中,形参声明后面的逗号是可选的。
(4)每个参数的类型可以不同,也可以相同
(5)与无参函数有别
(6)省略号的优先级别最低,所以在函数解析时,只有当其它所有的函数都无法调用时,编译器才会考虑调用省略号函数的
(7)在传递与取用的时候,要类型一一对应
#include <stdio.h>
#include <stdarg.h>
void ArgFunc(const char *str ... )
{
va_list ap;
int n = 3;
char *s = NULL;
int d = 0;
double f = 0.0;
va_start(ap, str); // 注意!这里第二个参数是本函数的第一个形参
s = va_arg(ap, char*);
//s = va_arg(ap, int); //编译不通过,会报错。所以说在传递与取用的时候,要类型一一对应
d = va_arg(ap, int);
f = va_arg(ap, double); // 浮点最好用double类型,而不要用float类型;否则数据会有问题
va_end(ap);
printf("%s is %s %d, %f \n\n ", str, s, d, f);
}
int main(void)
{
ArgFunc("The answer", "Hello", 345, 788.234);
}
运行截图:
参考学习:参考学习1
参考学习:参考学习2
4.局部与整体
不要返回局部对象的引用和指针。也就是说局部的东西会在局部(一般是函数)执行完就会被释放,返回的时候要考虑这一点。
5.默认实参
一种形参但在多次调用中都反复使用同一个值,可传参也可不传参,不传参就使用默认值,传参就使用所传入的实参。一般在函数声明中指定,放在合适的头文件中。
void err_msg(initializer_list<string> list1,int a = 0) //a 就是一个默认参数
6.内联函数
对于较小的函数(为了程序的“好看性”),我们可能真正执行的就那么一小会,但是对于函数的调用可能花费很长的时间。也就是说函数调用花费的时间会比函数真正起作用的时间要长的多,如果频繁调用程序效率就会非常低下,(类比:线程池等)那么我们就会用到内联函数来解决这个问题了
内联函数是指用inline关键字修饰的函数。在类内定义的函数被默认成内联函数。内联函数从源代码层看,有函数的结构,而在编译后,却不具备函数的性质。
内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处。编译时,类似宏替换,使用函数体替换调用处的函数名。内联说明只是向编译器发出一个请求,编译器可以选择忽略这个请求
#include<iostream>
using namespace std;
/*
* struct timespec {
2 time_t tv_sec; //seconds
3 long tv_nsec; // nanoseconds 納秒
4 };
*/
timespec diff(timespec start, timespec end)
{
timespec temp;
if ((end.tv_nsec-start.tv_nsec)<0) {
temp.tv_sec = end.tv_sec-start.tv_sec-1;
temp.tv_nsec = 1000000000 + end.tv_nsec - start.tv_nsec;
} else {
temp.tv_sec = end.tv_sec-start.tv_sec;
temp.tv_nsec = end.tv_nsec-start.tv_nsec;
}
return temp;
}
inline void fun1(int temp)
{
for(int i = 0 ;i != 10000 ;++i)
;
}
int main(void)
{
timespec time1, time2;
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &time1); // 一个进程的时间
for(int i= 0;i< 400000;++i)
fun1(10); //将这里进行内联与非内联的替换
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &time2);
cout<< "秒数是:"<< diff(time1,time2).tv_sec<< " 纳秒数是 :"<< diff(time1,time2).tv_nsec<<endl;
}
运行截图:
由结果可以看出,内联函数的确是有一点功效的。
7.constexpr函数
不想写了–>>constexpr函数
8. 函数指针
(1)void (*fun)(int a,int b); //fun就是一个函数指针
注意事项:不存在智能转换
#include<iostream>
using namespace std;
int compare_0(const int &a,const int &b)
{
return a-b;
}
double compare_1(const int &a,const int &b)
{
return a-b;
}
int main(void)
{
int (*p)(const int &,const int &);
p=compare_0; //不存在智能转换
cout << p(12,11) << endl ;
}