C++基础
inline函数:
- inline函数不能在头文件中声明,需要放在源文件中声明。
- inline函数在不同的源文件的实现和调用会导致函数链接错误,所以不能在同一个项目中的不同源文件内定义函数名相同但实现不同的inline函数。
- 一般代码少、调用频繁的函数适用inline函数。
引用
引用定义:
定义: 引用不是引入一个新的变量,而是给已经存在的变量取了一个别名。因此编译器也不会给引用变量开辟一个新的空间,它和它的引用的变量共同使用同一个内存空间。
引用的定义打个比方就是比如孙悟空和孙大圣的概念,两者实质是一个人;而引用概念也是一样,实质上共同使用同一个内存空间地址。
我们可以通过简短代码验证一下:
#include<iostream>
using namespace std;
void TestRef()
{
int a = 1;
int& b = a;
printf("%d %d\n", a, b);
}
void TestRef1()
{
int a = 10;
int& b = a;
int& c = a;
printf("%p %p %p\n", &a, &b, &c);
}
int main()
{
TestRef();
TestRef1();
return 0;
}
结果如下:可以验证三者实质上共同使用同一块内存空间。
引用规则:
- 引用必须初始化:
如图所示,如果引用比进行初始化,编译器显然会报错。 - 一个变量可以有多个引用变量:
void TestRef1()
{
int a = 10;
int& b = a;
int& c = a;
printf("%p %p %p\n", &a, &b, &c);
}
前面的例子中就已经验证了可以一个变量有多个引用变量。
- 引用一旦引用一个实体,再不能引用其他实体。
举一个简单的代码小例子:
void Test()
{
int a = 10;
int& b = a;
int c = 20;
b = c;
printf("%p %p %p\n", &a, &b, &c);
printf("%d %d %d", a, b, c);
}
int main()
{
Test();
return 0;
}
运行结果如图所示:
可以得出结论:在b=c;的句子中是复赋值关系,一个引用一旦引用一个实体,再不能引用其他实体。
常引用:
void TestConstRef()
{
const int a = 10;
//int& b = a; 此句会报错,因为权限放大,const修饰不可以修改a的值,使用此句话会权限放大,正确使用方法如下:
const int& b = a;
int c = 20;
int& d = c;
const int& e = c;//此句话不会报错,这句话是权限缩小,const修饰,e的值不会改动。
double pi = 3.1415;
//int& f=pi;此句话会报错,类型不同不能直接进行引用。
//可以进行缩小权限进行类型转换
const int& f = pi;//类型转变,中间产生临时变量。
}
同时我们应该注意的是:临时变量具有常属性、类型转换的时候会产生临时变量。如上面的代码中,实质上f是临时变量的别名。
引用的使用:
- 传参时,函数会产生临时变量,
- - 传引用返回,返回对象是对象的别名(引用)没有开辟另外的空间
修改返回值可以利用引用:
int& At(int i)
{
static int a[9];
return a[i];
}
void TestAt()
{
for (int i = 0; i < 9; i++)
{
At(i) = i + 10;
}
for (int i = 0; i < 9; i++)
{
cout << At(i) << " ";
}
cout << endl;
}
int main()
{
TestAt();
return 0;
}
运行结果如图所示:
而如果此时如果去掉&时,此时不能发生返回值的改变,因为如果不带&,会产生临时变量,变量是右值,不能改动。
运行图如下:
- 做参数
void Swap(int& left, int& right)
{
int tmp = left;
left = right;
right = tmp;
}
- 做返回值
给出代码如下:
int& Add(int a, int b)
{
int c = a + b;
return c;
}
//引用返回的意思是:不会产生临时变量,直接返回c的引用。
int main()
{
int& ret = Add(1, 2);
cout << "Add(1,2) is: " << ret << endl;
Add(3, 4);
cout << "Add(1,2) is: " << ret << endl;
return 0;
}
运行结果如图所示:
根据结果可知,最后的执行结果ret是Add(1,2)和Add(3,4);所产生的结果。
我们可以知道当前代码的问题:
- 存在非法访问的问题,因为Add(1,2)的返回值是c的引用,所以Add栈帧销毁以后,回去访问c位置的空间。
- 如果Add函数栈帧销毁、清理空间,那么取值的时候得到的就是随机值,给ret就是随机值。
所以我们应该注意:如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已
经还给系统了,则必须使用传值返回。
auto关键字
auto基本概念:
C++11中作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
int Const()
{
return 10;
}
void TestAuto()
{
int a = 10;
auto b = a;
auto c = 'a';
auto d = Const();
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
}
int main()
{
TestAuto();
return 0;
}
运行结果如图:
实际生活中,我们并不是像上面一样使用auto关键字,实际运用如图所示:
std::map<std::string, std::string> dict = {{ "sort", "排序" }, { "insert", "插入" }
};
std::map<std::string, std::string>::iterator it = dict.begin();
// 根据右边的值去自动推导it的类型,写起来就方便了
auto it = dict.begin();
注意: 使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型,因此auto并非是一种“类型”的声明。而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。
auto的使用规则:
- auto与指针和引用结合使用。
auto声明指针类型的时候,用auto和auto*没有任何区别,但auto声明引用时必须加上&
例:
void TestAuto1()
{
int x = 10;
auto a = &x;
auto* b = &x;
auto& c = x;
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
printf("%p %p %d", a, b, c);
}
int main()
{
TestAuto1();
return 0;
}
结果如下:
- auto在同一行定义多个变量
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器会报错,因为编译器实际上对第一个类型进行推导,然后用推导出来的类型定义其他变量。
如图所示:
auto不能推导的场景:
- auto不能作为函数的参数
此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{
}
- auto不能直接用来声明数组
基于范围的for循环(C++11)
在C++98中我们在C/C++中的遍历方式如下:
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
array[i] *= 2;
for (int* p = array; p < array + sizeof(array)/ sizeof(array[0]); ++p)
cout << *p << endl;
}
在C++11中我们使用for循环后的括号**由冒号“:”分为两部分:**一部分是范围内用于迭代的变量,另一部分则表示迭代的范围,通常还可以与auto结合使用。
void TestFor()
{
int a[] = { 1,2,3,4,5,6 };
for (auto e : a)
cout << e << " ";
printf("\n");
}
int main()
{
TestFor();
return 0;
}
运行结果如图所示:
同时还可以利用参数进行数组中的值修改:
void TestFor1()
{
int a[] = { 1,2,3,4,5,6 };
for (auto& e : a)
e *= 2;
for (auto e : a)
cout << e << " ";
printf("\n");
}
int main()
{
TestFor1();
return 0;
}
注意: 范围for循环迭代的范围必须是确定的
对于数组而言,就是数组的第一个元素和最后一个元素的范围。下面的代码就会出现问题:
指针控制nullptr(C++11)
NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
可以知道在C语言中NULL的默认值为0,或者被定义为无类型指针(void*)的常量。
给出例子如下代码:
void f(int)
{
cout << "f(int)" << endl;
}
void f(int*)
{
cout << "f(int*)" << endl;
}
int main()
{
//C++98
int* p1 = NULL;
int* p2 = 0;
//C++11中初始化空指针使用nullptr
int* p3 = nullptr;
f(NULL);
f(0);
f(nullptr);
return 0;
}
运行结果:
我们知道:f(NULL)我们想要得到的理想值是f(int*),但是NULL的默认值为0,所以运行结果为:f(int)。所以在C++中最好使用空指针为nullptr;