该博客用于记录自己在阅读过程中不懂的知识点,很少用到但比较重要的知识点以及模棱两可的知识点
只存在与函数执行期间打对象叫自动对象,与之对应的是局部静态对象
传参时可以只用引用避免拷贝,拷贝大的对象效率低而且有些类类型不支持拷贝,使用引用还可以返回额外信息
实参初始化形参时会忽略顶层const,这就意味着
void func(const int i);
void func(int i);
并不是重载函数,会报出重复定义动错误,第一个func既可以传入constint也可以传入 int但func并不能向i写值。
形参的初始化和变量的初始化是一样的
voidprint(const int[10]); //这里的10只是我们的期望值,实际上不一定,因为传入的实际上是指针为了限制数组的规格,我们一般采用3
种方法:
1.使用标记//’\0’
2.使用标准库规范//begin和end指针
3.显式的传入一个表示数组规格的形参
如果数组不需要写操作,我们一般把数组形参写成指向const的指针
我们也可以把形参写成数组动引用,此时需要带上数组动规格, 例:
void print(int (&arr)[10]){
/*
*
*/
}
但这样写无疑是限制例我们只能将函数作用于大小为10的数组
二维数组的形参定义
void print(int ( *matrix) [10] ){}
void print(int matrix[][10]){} //等价
main函数处理命令行选项 :
aegc 是一个int型变量用于表示argv字符数组中的字符串数量,举个例子帮助理解
prog-d -o ofile data0
argv[0]= “prog”;
argv[1]= “-d”;
argv[2]= “-o”;
argv[3]= “ofile”;
argv[4[= “data0”;
argv[5]= 0;
含有可变形参的函数
形参类型相同时:initializer_list与vector类似,不一样的是这个对象中的元素永远是常量
.size()表示对象中的元素数量
.begin()和.end()首尾指针
值得一提的是initializer_list对象之间赋值时不会拷贝列表中的元素/*list2(list)*/ /*list2=lsit*/,拷贝完成后原始列表和副本共享元素
传参时不仅可以传initializer_list对象也可以传一对花括号包裹的值序列
当形参类型不同时我们可以编写一种特殊的函数,也就是所谓的可变参数模板(后面再提)
省略符形参是为了方便C++程序访问某些特殊的C代码而设定的,这些代码使用了名为varargs的C标准库功能。通常省略符形参不应
该用与其他目地。
函数返回一个值的方式和初始化一个变量或形参的方式完全一样
返回类型为引用的函数得到左值,其他类型得到右值,可以像正常左值一样使用返回函数的引用的调用
/*func(a).size */ //func是一个返回非常量引用的函数
也可以对返回非常量引用的函数的结果赋值
/*func(a) = “asda” */
函数也可以返回花括号包裹的值列表
vector <string> func(){
return{“a”, “bb”, “ccc”};
}
main函数的返回值并不是void,返回0表示执行成功,如果结尾没有return语句,编译器会隐式的插入一条return0;返回非0值表示执行失
败,具体含义由机器决定,为了使返回值与机器无关。cstdlib定义了两个预处理变量(由预处理器定义,不能加std,也不能加using)
return EXIT_FAILURE;
return EXIT_SUCCESS;
返回一个数组的指针或引用的函数比较繁琐,常用别名来简化操作
typedef int arr[10];
using arr = int[10]; //arr是 类型为10个整数的数组的别名
arr* func(int i); //返回值是一个指向含有10个整数的数组的指针
不用别名声明一个返回数组指针的函数
int (*p)[10] = &arr; //p2是一个指针,它指向含有10个整数的数组
我们可以从上面得到启发,不使用别名:
int ( *func ( int i) ) [10];
使用尾置返回类型依然可以简化上述操作
auto func(int I) -> int(*)[10];
也可以使用decltype
int odd[] = {1, 3, 5, 7, 9}; //odd是一个含有5个整型元素的数组
decltype(odd) *func(int I);
函数重载时顶层const的有无是等价的,会导致重复定义
int func(int i);
int func(const int i);
int func(int *i);
int func(int *const I);
底层const可以构成重载
int func(int &i);
int func(const int &i);
int func(int *i);
int func(const int *i);
const_cast和重载,填前面的坑
const string &shortStr(cosnt string &s1, const string &s2){
returns1.size() <= s2.size() ? s1 : s2;
}
然而虽然该函数可以传进 常量和非常量 引用,但它返回的结果是常量引用
所以我们可以写一个重载函数将形参定为非常量引用,再用一个常量引用的副本接受上面那个函数的返回值(其中形参可以通过
const_cast转换为常量引用),然后在返回的时候再转换为非常量引用
string& shortStr(string &s1, string &s2){
auto &r = shortStr(const_cast<const string&> (s1),const_cast<const string&>(s2));
return const_cast<string &>(r);
}
当一个函数多次声明时,后续声明不能修改前面声明的默认实参,并且要保证右边的形参全部都有默认实参
constexpr函数可以作用于常量表达式,但其返回值和形参都必须是字面值类型且只有一个return语句,概括的说cosntexpr函数在编译期
间就必须能够知道函数的所有信息,constexpr函数被隐式的指定为内联函数,因为在编译的时候编译器就要将内联函数展开,所有内
联函数的声明和定义一般都写在头文件中
为了帮助调试程序,产生例许多帮助调试的东西
1.assert(expr)这是一种预处理宏,由预处理器管理,如果expr表达式为0则终止程序执行,如果为假则什么也不做。和其他预处理变量
一样,使用它不需要std::和using
assert预处理宏的执行依赖于一NDEBUG预处理变量的定义,如果定义了NDEBUG,则表示关闭例调试状态,无论expr表达式是什么
assert都不执行
因为NDEBUG的定义用到的是#define,所以,我们可以灵活使用它,而不仅仅局限于使用assert函数,比如我们可以像条件编译一样
使用它:
#ifndef NDEBUG
/*
*此处可以写自己的调试代码
*/
#endif
编译器还定义了一个__func__变量用于表示当前函数的名字,除此之外预处理器也定义了4个预处理变量:
__FILE__存放文件名的字符串字面值;
__LINE__存放当前行号的整型字面值;
__TIME__存放文件编译的时间的字符串字面值
__DATE__存放文件编译日期的字符串字面值
当调用一个函数传入的实参可以和多个重载函数匹配,并且难以判断孰优孰劣,则函数调用会产生二义性,编译器会拒绝这个请求,例:
void func(int i, int j);
void func(double i, double j);
当调用时,func(1,1.2),对于第一个实参1,第一个函数更匹配,对于第二个实参1.2,第二个函数更匹配,则不能比较出调用谁更合适
(虽然可以相互转换),则会产生二义性
既然编译器会比较谁更匹配,那么必然就有一个标准来决定匹配的优先级:
1.精确匹配:
实参和形参类型完全相同
从数组类型或函数类型(函数指针)转换为指针类型
顶层const的转换
2.底层const间的转换实现的匹配
3.整型提升实现的匹配
4.算术转换或指针转换实现的匹配
5.类类型转换实现的匹配
整型提升的过程中,并不少就近原则,所有小整型都会提升为int,如
void func(int);
void func(short);
func(‘a’); //将会提升为int调用第一个,如果想调用第二个反而会导致类型转换,这里要提一下, char是一种特殊的整型
所有类型转换的优先级都一样,如:
void func(long);
void func(float);
func(3.14); //将会产生二义性,因为都发生了类型转换,值得一提的是3.14默认是double类型的,3.14f才是float类型的
函数指针:
函数声明:bool func(const string &, const string &);
该函数类型的指针bool (*p)(const string &, const string &); //函数类型由返回值和形参类型决定,该指针未初始化
bool *p(const string &, const string &); //括号必须带上,否则为返回值为bool类型指针的函数的声明
p= func;
p= &func;//两个赋值方法是等价,&可省略
与上面的相同,当使用函数指针时不需要解引用指针
bool p1 = p(“aaa”, “bbb”);
bool p1 = (*p)(“aaa”, “bbb”);
bool p1 = func(“aaa”, “bbb”); //三者等价
函数指针不存在类型转换规则
使用函数指针时,指针类型必须与函数类型精确匹配,意味着就算形参类型可以通过转换得到也不行
void func(unsigned int);
void (*p)(int) = func; //错误,unsigned int和int不是精确匹配
与数组相同,形参类型虽然不能是函数类型(也可以写成函数类型,不过会自动转换成指向函数的指针类型),但可以是指向函数的指
针类型
void func(int);
void test(void (*p)(int)); //test的形参是一个函数指针
然而这样写会显得很冗长,类型别名是一个不错的选择:
typedef void (*f)(int);指向形参为1个整型,返回值为void的函数的指针的类型别名为f
typedef decltype(func) *f; //等价
使用类型别名后:
void test(f);
同理,虽然不能返回一个函数,但可以返回一个指针,然而与形参不同的是,函数类型并不会转换成指针类型
using PF = void (*)(int);
using F = void (int);
PF f1(); //正确,PF是指向函数的指针,所有f1的返回值是指向函数的指针
F f1(); //错误,F是函数类型,f1不能返回一个函数
F *f1(); //正确,显式的将返回类型指定为指向函数的指针
没有用到别名的写法: void (*f1())(int);
同样也可以使用decltype简化
int f1(int);
decltype(f1) *returnF1(); //返回值是一个指向f1的指针