一、C++的关键字
二、命名空间namespace
1、命名空间的作用就是为了对标识符进行隔离,避免相同的名字产生冲突
namespace N1//N1是命名空间的名称
{
//命名空间的内容既可定义变量也可定义函数
int a = 2;
int ADD(int x, int y)
{
return x + y;
}
}
namespace N2
{
int a = 3;
int b = 5;
int ADD(int x, int y)
{
return x + y;
}
namespace N3 //命名空间可嵌套使用
{
int a = 5;
int b = 7;
void Swap(int *pa, int *pb)
{
int tmp = *pa;
*pa = *pb;
*pb = tmp;
}
}
}
注:一个命名空间就定义了一个新的作用域,命名空间中所有内容都只局限于该命名空间
2、命名空间使用
(1)加命名空间名称和作用域限定符::
int main()
{
int a = 3;
int b = 4;
printf("%d\n", N1::a);//输出2,输出的是命名空间N1中的a
printf("%d\n", N2::a);//输出3,输出的是命名空间N2中的a
printf("%d\n", N2::N3::a);//输出5,输出的是命名空间N3中的a
N1::ADD(2, 3);
N2::N3::Swap(&a, &b);
return 0;
}
(2)使用using将命名空间中的成员引入
using N1::ADD;
int main()
{
int c = ADD(3, 4);
printf("%d\n", c);
return 0;
}
(3)使用using namespace 将命名空间引入
using namespace N2;
int main()
{
printf("%d\n", a);//输出N2中a的值3
return 0;
}
三、C++的输入与输出
#include<iostream>
//using namespace std;
using std::cout;
using std::cin;
using std::endl;//换行
int main()
{
int a;
double b;
cin >> a >> b;//从键盘中输入a = 2,b = 3.14
cout << a << endl;//输出到控制台a,b的值
cout << b << endl;
return 0;
}
四、缺省参数
在声明或是定义函数时为函数的参数指定一个默认值,在调用函数时若是没有传参则采用该默认值,否则使用实参
#include<iostream>
using namespace std;
//全缺省参数
void Test(int a = 10, int b = 20, int c = 30)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
半缺省参数,调用该函数时必须给a传参
注:1、半缺省参数必须从右往左依次缺省,不能间隔着给
注:2、缺省参数不能在函数定义和声明中同时出现,若同时出现且缺省值不同,系统无法判断使用哪一个
void Test1(int a, int b = 20, int c = 30)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
int main()
{
Test();//输出10,20,30
Test(1);//输出1,20,30
Test(1,2);//输出1,2,30
Test(1,2,3);//输出1,2,3
Test1(1);
Test1(1,2);
Test1(1,2,3);
return 0;
}
五、函数重载
5.1、在同一个作用域中声明几个功能类似的同名函数,这些函数的形参列表(参数个数 或 类型 或 顺序)必须不同
#include <iostream>
using namespace std;
int ADD(int x, int y)
{
return x + y;
}
double ADD(double x, double y)
{
return x + y;
}
int main()
{
int c = ADD(3, 4);
double d = ADD(3.14, 2.34);
cout << c << endl << d << endl;
return 0;
}
5.2、程序运行需经历的过程
预处理:展开头文件,宏替换,条件编译,去掉注释,Linux下将文件处理为main.i
编译:检查语法错误,生成汇编代码 Linux下将文件处理为main.s
汇编:把汇编代码转换为二进制机器码 Linux下将文件处理为main.o
链接:将各个处理好的文件链接到一起,生成可执行程序 Windows下处理为xxx.exe,Linux下处理为a.out
5.3、名字修饰(name Mangling),指在编译过程,将函数、变量的名称重新改编,生成全局中唯一的名称,便以区分各个函数和变量
C语言中名字修饰的方法十分简单,只是在函数名字前添加下划线,eg:_ADD,因此C语言不允许函数重载
C++中修饰函数名包含函数的名字和参数类型以及函数返回值类型,eg:int_cdecl ADD(int,int) (?ADD@@YAHHH@Z)因此即便函数名相同,只要参数列表不同,在通过编译器重新修饰之后,函数名在底层全局也是唯一的,这也是C++允许函数重载的原因
5.4、为了支持C语言调用C++模块中用C语言规则写的函数,于是在函数前加extern “C”,告诉编译器将该函数按照C语言规则来编译
extern "C" int Add(int x, int y)
{
return x + y;
}
int main()
{
ADD(3, 4);
return 0;
}
六、引用
6.1、引用的定义:不是重新定义一个变量,而是给已经定义了的变量取一个新的名字,即别名,此时编译器不会为引用变量开辟新的内存空间,引用变量和其引用的变量共用同一个内存空间。
类型& 引用变量名(对象名) = 引用实体
#include <iostream>
using namespace std;
void TestRef()
{
int a = 10;
int& b = a;//定义引用类型,引用类型必须和引用实体是一个类型的
printf("%p\n", a);//0000000A
printf("%p\n", b);//0000000A
}
int main()
{
TestRef();
return 0;
}
6.2 引用特性
(1)引用在定义时必须初始化,否则无法得知是在引用哪个变量
(2)一个变量可以有多个引用,就如一个人可有多个名字
(3)引用一旦引用了一个实体就不能引用其他实体
6.3 常引用
void TestConstRef()
{
const int a = 10;
//int& b = a; 此声明引用类型是错误的
const int& b = a;
double c = 3.14;
double& d = c;
const int& e = c;//此类声明可行,因为在类型转换时中间会产生临时变量,
//该临时变量是不可改变的常量,此时引用必须加上const
}
6.4 使用场景
1、做参数
void Swap(int& x, int& y)//此时参数x,y是分别实参a,b的别名,x和a表示的是同一个内存空间,y和b表示的是同一个内存空间交换x,y即交换a,b
{
int tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 10;
int b = 20;
Swap(a, b);
return 0;
}
2、做返回值
int TestReturn(int a)
{
a += 10;
return a;
//此时不会用a返回,因为a是局部变量,出了TestReturn函数之后就会被销毁,
其栈上的空间也会还给系统,因此不能用栈上的空间作为返回值返回a的值是在执行
TestReturn函数时就在main函数的栈帧中为a创建一个临时空间存储a的值,
TestReturn函数执行完后,再将临时空间中的值赋值给y
}
//使用引用做参数,返回值得生命周期不受函数的限制
int& TestReturn1(int& a)
{
a += 20;
return a;
}
int main()
{
int x = 10;
int y = TestReturn(x);
return 0;
}
6.5 传值、传引用、传指针的效率比较
传值效率最低,因为需要开辟临时空间,才能接受所传递的值
传引用、传指针效率几乎相同,都不需要重新开辟空间
6.6 引用和指针的区别
1、引用在定义时必须初始化,指针不需要
2、引用在初始化时引用一个实体后,就不能引用其他实体,指针可以在任何时候指向任意一个同类型的实体
3、没有NULL引用,有NULL指针
4、在sizeof中的含义不一样:引用的结果是引用实体类型的大小,指针始终是地址空间所占字节个数,
指针的大小与编译器配置的程序位数有关,32位下指针是4个字节,64位下指针是8个字节
5、引用自加即引用实体自加,指针自加即指针向后偏移一个类型的大小
6、有多级指针,没有多级引用
7、访问实体的方式不同,指针需要显示解引用,引用编译器字节处理
8、引用比指针使用起来更安全,指针会出现野指针的情况
七、内联函数(inline)
7.1 内联函数的定义:使用时编译器会把inline标注的函数在调用内联函数处展开,没有函数压栈开销,是以空间换时间的做法
但是代码很长或者有循环/递归的函数不适合做内联函数
inline对于编译器只是一个建议,编译器会自动优化,如果内联函数代码很长或者有循环/递归,编译器优化时会忽略内联函数
inline不建议声明与定义分离,会导致链接错误,因为在展开inline函数时找不到函数体,没有函数地址,链接找不到
宏函数
ADD的宏函数
#define int ADD(int a,int b) return a+b
#define ADD(a,b) a+b
#define ADD(a,b) ((a)+(b))
//Swap的宏函数
#define Swap(x,y)\
(x) = (x)+(y); \
(y) = (x)-(y); \
(x) = (x)-(y)
int main()
{
int c = ADD(2, 3);//预处理时替换为 int c = ((2)+(3));
int d = ADD(2 + 3, 4) * 3;// 预处理时替换为 int d = ((2+3)+(4))*3;
Swap(c, d);
cout << c << endl;
cout << d << endl;
return 0;
}
八、关键字auto,auto声明变量必须由编译器在编译时期推导而得
int TestAuto()
{
return 10;
}
int main()
{
int a = 10;
auto b = a;
auto c = 'a';
auto d = TestAuto();
//auto e;无法通过编译,使用auto定义变量时必须初始化
cout << typeid(b).name() << endl;//int
cout << typeid(c).name() << endl;//char
cout << typeid(d).name() << endl;//int
return 0;
}
8.1 auto的使用细则
(1)auto与指针和引用结合起来使用
int main()
{
int x = 10;
auto a = &x;
auto* b = &x;
auto& c = x;
cout << typeid(a).name() << endl;//int*
cout << typeid(b).name() << endl;//int*
cout << typeid(c).name() << endl;//int
*a = 20;
*b = 30;
c = 40;
return 0;
}
(2)在同一行定义多个变量,变量类型必须是相同的类型,否则编译器会报错,
因为编译器只对第一个变量的类型进行推导,然后用推导出来的类型定义其他变量
void TestAuto()
{
auto a = 1, b = 2;
auto c = 3, d = 4.0;//编译失败,因为c和d初始化的表达式类型不同
}
8.3 auto不能推导的场景
(1)不能作为函数的参数,因为函数只有在被调用时才会接收到
void TestAuto(auto a)
{}
值,在此之前编译器无法对a的实际类型进行推导
(2)auto不能直接声明数组
void TestAuto()
{
int a[] = { 1, 2, 3 };
auto b[3] = a;
}
九、基于范围for循环
9.1范围for的语法
void TestFor1()
{
int array[] = { 1, 2, 3, 4, 5, 6 };
for (int i = 0; i < sizeof(array) / sizeof(int); ++i)
{
array[i] *= 2;
}
for (int i = 0; i < sizeof(array)/sizeof(int); ++i)
{
printf("%d ", array[i]);
}
}
void TestFor2()
{
int array[] = { 1, 2, 3, 4, 5, 6 };
for (auto& e : array)
e *= 2;
for (auto& e : array)
cout << e << " ";
}
int main()
{
TestFor1();
TestFor2();
return 0;
}
9.2 范围for的使用条件:for循环的迭代范围必须是确定的
void TestFor(int array[])
{
for (auto& e : array)//for循环范围不确定
cout << e << " ";
}
十、指针空值nullptr
10.1 表示指针指向空时相当于 *ptr = NULL,nullptr的类型是nullptr_t