自动对象
只存在于块执行器件的对象称为自动对象,当块的执行结束后,块中创建的自动对象的值变成未定义
局部静态对象
在经过定义语句时候对静态对象进行初始化,直到程序结束后对象才真正销毁
静态对象的默认初始值为0
函数声明
最好是将一些变量和函数的声明整合到一起,单独放在一个头文件内,方便后续管理
添加头文件
在vscode上进行配置后
我这里头文件都是用Mingw进行安装的
在调用这些自带的头文件要用括号括起<>
如果是自己写的头文件,则放到同一个目录下,使用双引号括起!!!
下面是CHAPTER6.h的内容
static int a;
接着是主程序
# include"CHAPTER6.h"
# include<iostream>
using namespace std;
int main(){
cout<<a<<endl;
}
参数传递
参数传递主要分为传值传递和引用传递
传值传递就是将实际值传递给形参,通过形参进行计算
而引用传递则是绑定了一个引用在已有的变量上,然后在其本身上进行计算
引用传递所带来的好处就是,避免了大型对象操作时先进行拷贝,再进行计算的过程。能节省大量运算时间
函数返回多个值
第一个方法就是建立一个新的数据类型,让其包含多个类型值
第二个方法就是在函数定义时,增加一个引用
例子:统计一个字符串数值,并且对原始字符串与别的字符串进行拼接
# include<iostream>
# include<string>
using namespace std;
string count(string s, int& crt){
crt = s.size();
return (s+"end!");
}
int main(){
string s("ZZK!");
int crt = 0;
string s2 = count(s, crt);
cout<<s2<<endl;
cout<<crt<<endl;
}
这里我们的count就返回的时字符串拼接后的结果,而绑定了一个int引用crt,来对字符串内的字符进行统计
const形参和实参
第二章谈论到,顶层const就是其对象本身是一个常量
而在参数传递中,形参的顶层const被忽略掉了,也就是当形参用顶层const时候,传给它常量对象或者非常量对象都是可以的
尽量使用常量引用
首先常量引用能保护实参的值不被修改
其次常量引用能让函数接受更多类型的参数,而不会产生错误
比如我们上面写的count函数,它使用的是string对象
因此只能调用string对象
但如果我们想要用一个字符串字面值来传递参数
那我们需要把引用改成常量引用
string count(const string &s, int& crt){
crt = s.size();
return (s+"end!");
}
string s2 = count("sdasdsada", crt);
这样进行字面值参数传递就不会报错
数组形参
C++中不允许我们拷贝数组,因此如果传递数组到参数的时候,实质上是传递指向数组首元素的指针
通常数组管理有以下几种方法
通过标记指示数组
比如字符数组中,结束标记为’\0’,我们对数组进行操作的时候,每次都校验指针内容是否为空,若不为空再进行后续操作
使用标准库begin 和end
标准库begin 和 end 为我们提供了数组首尾的指针,我们可以根据这一特性对数组进行操作
显式指定数组大小
我们可以在参数传递同时,传递一个size_t 类型的参数,表示数组的大小
另外,不需要对数组进行写操作的时候,我们可以将数组定义为一个指向const的指针
即const int *p[]
数组引用形参
我们可以传递数组的引用作为形参,前提是在两边加上括号
void print(int (&arr)[10]){
for(auto elem:arr){
cout<<elem<<endl;
}
}
一定要在&arr两边加上括号,这才表示的是数组的引用
如果是&arr[10] 则表示的是引用的数组
传递多维数组
多维数组就是数组的数组
因此我们传递的时候可以用一个指针代表首元素,这个元素它又指向的是一个含有多个数值的数组
void print(int(*matrix)[10], int rowSize)
等价于
void print(int matrix[][10])
处理命令行
int main(int argc, char **argv)
第一个形参指定的是形参数量,第二个形参传递的是指向字符串的指针
含有可变形参的函数
c++11提供了两种主要的方法
1.如果所有的实参类型相同,可以传递一个名为initializer_list的标准库类型
2.如果实参类型不同,可以编写一个特殊的函数,就是可变参数模板
initializer_list
它跟vector一样,也是模板类型,而且操作也基本相同
begin 和end方法都是返回首尾元素的指针
size返回整个列表的大小
我们在使用它的时候需要指定实参类型
initializer_list<string> ls;
void error_msg(initializer_list<string> s1){
for(auto beg=s1.begin();beg!=s1.end();beg++){
cout<<*beg<<endl;
}
}
string i1("akakd"), i2("sadasd"), i3("dihiq");
error_msg({i1, i2, i3});
传递参数进行调用的时候,一定一定要用花括号框起来!!!
省略符形参
这是为了便于C++访问某些c程序中所设定的
但在大部分情况下,并不使用编程,故不介绍了
返回值
在含有return语句的循环后面,应该也有一条return语句用于处理额外的情况
不要返回局部对象的引用和指针
函数是一种局部域
函数执行完后,局部对象也就随之消失,因此对应的内存空间就会被释放,如果你返回的是一个局部对象的引用,那么这个引用绑定的内存空间其实是会被释放的,因此是不安全的。
引用返回左值
引用返回的是左值,因此我们可以对此进行赋值,对引用进行修改
如果函数返回的是常量,那么我们直接赋值是会报错的
char &get_val(string &s, int ix){
return s[ix];
}
string s("iloveu");
get_val(s, 3) = 'T';
cout<<s;
输出结果:
iloTeu
在函数那里添加一个&表示这个函数返回的是引用
列表初始化返回值
函数可以返回一个花括号包围的值的列表
vector<string> expect(vector<string> s1){
if(s1.empty()){
return {};
}
else{
return {"easy", "dude"};
}
}
vector <string> s1{"sada", "sdadas"};
vector <string> s2 = expect(s1);
for(auto i:s2){
cout<<i<<endl;
}
返回函数自身
函数调用自身我们常常称为递归
比如我们写一个阶乘的函数
int fac(int val){
if (val>1){
return fac(val-1)*val;
}
else{
return 1;
}
}
返回数组指针
第一种方法就是使用类型别名
typedef int arrT[10];
using arrT = int[10];
arrT* func(int i); // 定义一个返回10个元素数组的指针的函数
int odd[] = {1, 3, 5, 7 ,9};
int even[] = {2, 4, 6, 8, 10};
arrT* array(int i){
return(i%2)? &odd : &even;
}
int i = 5;
auto a = array(i);
for(auto c:*a){
cout<<c;
}
第二种是直接声明
int (*func(int i))[10]
首先func(int i )表示这个函数接受一个int类型的参数
然后外层括号有一个解引用符号,说明func(int i) 是一个指针
最后有个[10] 说明这个指针指向的是含有十个元素的数组
int (*array(int i))[5]{
return(i%2)? &odd : &even;
}
第三种是使用尾置返回类型
auto func(int i)-> int(*)[10];
我们把函数返回类型放到了形参列表后面
表示返回一个指针,指向含10个元素的数组
示例
auto array(int i) -> int(*)[5]{
return (i%2)? &odd:&even;
}
第四种就是当我们知道要返回的数组的类型,就可以用decltype来声明返回类型
decltype(odd) *array(int i){
return (i%2)?&odd : &even;
}
函数重载
同名函数但参数列表不同构成重载
要注意
前面说过顶层const不影响传入对象
因此下面的两个函数是构成不了重载函数
A a1(phone);
A a1(const phone);
这两个函数的参数表是一样的,因此不构成重载
const_cast和重载
const string &shorterString(const string &s1, const string &s2){
return s1.size()<=s2.size()? s1:s2;
}
这是一段比较两个字符串长短的函数,函数将返回最短的字符串的引用
但是当我们传入的是两个非常量的string实参时候,它返回的结果依然是const string的引用
我们需要传入两个非常量的时候,返回普通引用
string &shorterString(string &s1, string &s2){
auto &r = shorterString(const_cast<const string&>(s1),
const_cast<const_string&>(s2));
return const_cast<string &>(r);
}
首先将它的实参强制转换成对const的引用,然后调用了shorterString的const版本。再转回一个普通的string引用
内联函数
关键字inline,实质上是用空间换取时间,在编译的时候,将代码段替换到调用该函数的地方。前提是该函数不能有复杂的循环,而且用的次数较多。它对编译器仅仅是一个建议,当编译器认为函数过于复杂,也不会让其内联
constexpr函数
constexpr函数 指用于常量表达式的函数
函数的返回值类型以及所有形参的类型都得是字面值类型
函数返回的不一定是常量类型
注意的是,内联函数和constexpr函数通常都放在头文件中,因为要保证其定义要一致
assert预处理宏
assert(expr)
对expr求值,如果表达式为假,则输出信息并终止程序
NDEBUG
在程序开头定义NDEBUG,则遇到了assert也不会退出程序
# define NDEBUG
一定是在程序第一行进行定义
它会让assert失效
函数指针
函数指针指向的是一个函数
我们使用函数名的时候,它会自动转换成一个指针
void test_func_pointer(){
cout<<"AMD yes!"<<endl;
}
auto pf = &test_func_pointer;
auto pf2 = test_func_pointer;
pf();
pf2();
加不加取地址符都是一样的
(也可以把auto改成void)
重载函数的函数指针
在重载函数中,我们必须指定其对应使用的函数的参数类型
void ff(int *);
void ff(unsigned int);
void(*pf1)(unsigned int) = ff;
需要明确指出,这里ff指向的是unsigned类型函数
函数指针形参
我们传递形参的时候,不能定义函数类型的形参,但可以以函数指针的形式传入
void Bigger(const int a, bool pf(const int a1, const int a2))
这里传入的pf是一个函数指针
我们也可以使用别名来简化函数指针书写
返回指向函数的指针
using F = int(int *, int);// F是一个函数类型!
using PF = int (*)(int *, int); // PF是一个指针类型;
我们也可以这么写
int (*f1(int))(int *, int)
f1有形参列表,因此f1是个函数
f1前面有个*,说明返回指针
指针内有个int,说明返回的是int类型
也可以使用尾置方式
auto f1(int) -> int(*)(int *, int);
使用auto和decltype来指定返回类型
当我们明确知道使用的是哪个函数的时候也可以使用decltype来确定返回类型