• C++入门必看知识点


    一、🌞命名空间

    1️⃣命名空间存在的意义是防止命名冲突
    2️⃣一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中

    1.1💡命名冲突

    我们知道变量、函数有很多,这些变量和函数大量存在于全局作用域中,会导致命名冲突,用strlen函数为例。
    如果要定义一个以strlen为变量名的变量,并且引了<string.h>,就会发生以下情况:

    #include<string.h>
    int strlen = 0;
    
    int main()
    {
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    error C2365: “strlen”: 重定义;以前的定义是“函数”.

    如果要解决这一问题,就要用到命名空间

    1.2💡命名空间的性质

    1️⃣命名空间中的内容,既可以定义变量,也可以定义函数

    // yyh为命名空间的名称
    namespace yyh
    {
    	//strlen为变量名
    	int strlen = 0;
    	int Add(int x, int y)
    	{
    		return x + y;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2️⃣命名空间可以嵌套

    // yyh为命名空间的名称
    namespace yyh
    {
    	int strlen = 0;
    	int Add(int x, int y)
    	{
    		return x + y;
    	}
    	namespace yyh1
    	{
    		int a = 0;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    3️⃣同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中(不同文件也行)

    // yyh为命名空间的名称
    namespace yyh
    {
    	int strlen = 0;
    	int Add(int x, int y)
    	{
    		return x + y;
    	}
    }
    
    namespace yyh
    {
    	int a = 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    但是如果我们在主函数里使用就会发现会出问题

    // yyh为命名空间的名称
    namespace yyh
    {
    	int strlen = 0;
    	int Add(int x, int y)
    	{
    		return x + y;
    	}
    }
    
    
    int main()
    {
    	printf("%d", Add(1, 2));
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    error C3861: “Add”: 找不到标识符

    1.3💡命名空间的用法

    命名空间的三种正确用法:

    1️⃣指定命名空间

    int main()
    {
    	printf("%d", yyh::Add(1, 2));
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这个方法写起来很麻烦,但是是最规范的方式

    2️⃣使用using(对部分展开)

    using yyh::Add;
    int main()
    {
    	printf("%d", Add(1, 2));
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这种方法可以对库里面常用的函数展开,比如:
    using std::cout;
    using std::endl;
    项目中经常用

    3️⃣使用using namespace

    using namespace yyh;
    int main()
    {
    	printf("%d", Add(1, 2));
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这种方法用起来很简单,比如用using namespace std;但是如果我们自己定义的东西跟库起冲突,就没办法解决了,所以在项目中不推荐


    二、🌞输入与输出

    输入并输出:“hello”

    #include<iostream>
    using std::cout;
    using std::cin;
    using std::endl;
    
    int main()
    {
    	char a[10];
    	cin >> a;
    	cout << a << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    跟C语言一样都需要头文件#include<iostream>
    可以发现跟C语言的区别就是C++不用用格式来输入输出,如%d, %s。


    三、🌞缺省参数

    缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该
    默认值,否则使用指定的实参。

    void Fun(int a = 1)
    {
    	cout << a << endl;
    }
    
    
    int main()
    {
    	Fun();
    	Fun(5);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    结果:
    1
    5

    3.1💡全缺省参数

    全部参数都给了缺省值

    void Fun(int a = 1, int b = 1)
    {
    	cout << "a = " << a  << " ";
    	cout << "b = " << b << endl;
    }
    
    
    int main()
    {
    	Fun();
    	Fun(2);
    	Fun(5, 5);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    结果:
    a = 1 b = 1
    a = 2 b = 1
    a = 5 b = 5

    3.2💡半缺省参数

    部分参数给了缺省值

    void Fun(int a, int b = 1, int c = 1)
    {
    	cout << "a = " << a << " ";
    	cout << "b = " << b << " ";
    	cout << "c = " << c << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    注意

    1️⃣半缺省参数必须从右向左依次给出,不能间隔
    2️⃣缺省参数不能同时出现在函数的声明和定义中
    例如在.h文件中有void Fun(int a = 1, int b = 1);
    那么在.cpp文件中就只能void Fun(int a, int b){}
    3️⃣缺省值必须是常量或者全局变量


    四、🌞函数重载

    4.1💡函数重载的用法

    C语言中不允许定义同名的函数
    而C++可以用函数重载定义同名的函数
    要求:

    形参列表(参数个数类型顺序)必须不同

    正确的函数重载:

    int fun(int a, int b) 
    {
    	return a + b;
    }
    double fun(double a, double b) 
    {
    	return a + b;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    int fun(int a, int b)
    {
    	return a + b;
    }
    int fun(double a, double b) 
    {
    	return a + b;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    错误的函数重载:

    int fun(int a, int b)
    {
    	return a + b;
    }
    int fun(int b, int a)
    {
    	return a + b;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    int fun(int a = 1, int b = 1)
    {
    	return a + b;
    }
    double fun(int a = 2, int b = 3)
    {
    	return a + b;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    但是有的构成重载但是不能用:

    void Fun(int a = 0, int b = 1) {}
    void Fun(int a = 0, int b = 1, int c = 2) {}
    
    
    int main()
    {
    	Fun(1, 2);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    虽然语法行得通,但是这样会导致编译器不知道调那个函数

    4.2💡函数名修饰规则

    首先想一个问题:为什么C++支持函数重载,而C语言不支持?

    4.2.1编译链接过程

    1️⃣预处理:头文件展开,宏替换,去掉注释,条件编译
    2️⃣编译:检查语法,生成汇编代码(指令)
    3️⃣汇编:把汇编代码转换成二进制机器码
    4️⃣链接:把文件链接到一起,生成可执行程序

    而如果在头文件调用函数,它会在链接时找到定义(地址)

    //Test.h
    int Add(int a, int b);
    double Add(double a, double b);
    
    //Test.cpp
    int Add(int a, int b)
    {
    	return a + b;
    }
    
    double Add(double a, double b)
    {
    	return a + b;
    }
    
    //main.cpp
    int main()
    {
    	Add(1, 2);//call Add(地址)
    	Add(1.1, 1.2);//call Add(地址)
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在C++中,所有的函数名都会被处理,例如上面的代码,第一个Add会变成_Z3addii,第二个就会变成_Z3adddd注意不光重载会改变名字,所有的函数都会按照函数名修饰规则改变
    但是C语言中,就没有函数名修饰规则,函数名为Add,就会那Add去找,所以不支持重载

    4.3💡extern"C"

    假设我们在C++中写了一个tcmalloc函数,而我们想在C语言中调用,在C++中函数会经过函数名修饰规则变成_Z8tcmallocui,而我们在C++文件中加上extern "C" void* tcmalloc(unsigned int n);,C++中符号表就会把_Z8tcmallocui变成tcmalloc(用C的规则),这样就可以调用了。


    五、🌞引用

    5.1💡引用的基本概念

    引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。

    用法:

    int main()
    {
    	int a = 10;
    	int& b = a;//b为a的引用
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    5.2💡引用特性

    1️⃣引用在定义时必须初始化
    2️⃣一个变量可以有多个引用
    3️⃣引用一旦引用一个实体,再不能引用其他实体

    int main()
    {
    	int& a;//error1
    
    	int b = 0;
    	int& c = b;
    	int d = 1;
    	c = d;//这里不是让c变成d的别名,而是把d的
    	      //值赋值给c(也就是b)
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    5.3💡常引用

    当变量被const修饰后,如果要引用,他的引用也要用const修饰(权限不能扩大,可以缩小)

    int main()
    {
    	const int a = 1;
    	int& b = a;//error,权限放大
    	const int& b = a;//correct
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    int main()
    {
    	int a = 10;
    	const int& b = a;//correct, 权限缩小
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    还有一点要注意:

    int main()
    {
    	double b = 1.3;
    	int c = 1;
    	c = b;//隐式类型转换
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    c = b这种类型转换的过程会有个int类型的临时变量,把b的值赋值给临时变量,c再接收。
    而这个临时变量具有常属性

    int main()
    {
    	double b = 1.3;
    	int c = 1;
    	c = b;
    	double& rc = c;//error
    	const double& rc = c;//correct
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这里的rc是c的临时变量的别名,强制类型转换也是一个道理

    5.4💡使用场景

    5.4.1做参数

    我们知道交换两个值的函数在C语言中要使用指针,不然交换的只是临时变量,而在C++中,我们可以使用引用

    void Swap(int& a, int& b)
    {
    	int tmp = a;
    	a = b;
    	b = tmp;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    5.4.2做返回值(特殊场景)

    1️⃣传值返回
    先来看一个场景

    int Add(int a, int b)
    {
    	int c = a + b;
    	return c;
    }
    
    int main()
    {
    	int ret = Add(1, 2);
    	const int& Ret = Add(1, 2);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这里Add函数返回c的值并不是直接返回,而是把c的值先给一个临时变量,再把临时变量返回。因为Add函数栈帧销毁后c也会消失,而临时变量不属于这个栈帧。
    2️⃣传引用返回

    int& Add(int a, int b)
    {
    	int c = a + b;
    	return c;
    }
    
    int main()
    {
    	int ret = Add(1, 2);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这里Add函数返回c的时候也会返回临时变量,但是临时变量的类型是int&,他返回的是c的
    引用(别名)
    ,但是函数结束后栈帧销毁,如果让ret接收,就会非法访问空间。
    如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已
    经还给系统了,则必须使用传值返回。

    以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是
    传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是
    当参数或者返回值类型非常大时,效率就更低

    所以只要符合条件,尽量选择传引用做参数和返回值

    5.5💡引用和指针❗️❗️

    引用和指针的不同点:

    1️⃣引用在定义时必须初始化,指针没有要求
    2️⃣引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型
    实体
    3️⃣没有NULL引用,但有NULL指针
    4️⃣在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占
    4个字节)

    5️⃣引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
    6️⃣有多级指针,但是没有多级引用
    7️⃣访问实体方式不同,指针需要显式解引用,引用编译器自己处理
    8️⃣引用比指针使用起来相对更安全


    六、🌞内敛函数

    6.1💡内联函数的概念❗️❗️

    以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,
    内联函数提升程序运行的效率。

    inline int Add(int a, int b)
    {
    	return a + b;
    }
    
    int main()
    {
    	Add(1, 2);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在C语言中为了避免栈帧的消耗,一些小函数可以用宏函数,在预处理阶段展开,那我们为什么要有内联函数呢(宏函数的优缺点)?

    优点:
    1️⃣增强代码的复用性
    2️⃣提高性能

    缺点:
    1️⃣不支持调试
    2️⃣宏函数语法复杂,易出错
    3️⃣没有类型安全检查(直接替换)

    例如写一个ADD的宏函数:#define ADD(x, y) ((x) + (y))
    为什么要这么多括号?
    防止优先级出现错误

    #define ADD(x, y) ((x) + (y))
    
    
    int main()
    {
    	int c = ADD(1 | 2, 1 | 3);
    	int d = ADD(1, 2) * 3;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    6.2💡内联函数的特性

    1️⃣如果代码很长或者有循环或递归的情况,不适宜用内联函数
    2️⃣inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联
    3️⃣ inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到


    七、🌞auto

    int main()
    {
    	int a = 0;
    	auto b = a;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    可以根据a的类型自动推出b的类型

    7.1 💡auto的使用细则

    1️⃣auto与指针和引用结合起来使用,用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&

    判断a,b,c的类型:

    int main()
    {
    	int x = 10;
    	auto a = &x;
    	auto* b = &x;
    	int& y = x;
    	auto& c = y;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    a: int*
    b: int*
    c: int (y的类型也是int)

    7.2💡auto不能推导的场景

    1️⃣auto不能作为函数的参数
    2️⃣auto不能直接用来声明数组

    void fun(auto x)//error1
    {}
    
    int main()
    {
    	int a[] = { 1, 2, 3 };
    	auto b[] = { 1, 2, 3 };//error2
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    7.3💡范围for

    int main()
    {
    	int a[] = { 1, 2, 3, 4, 5 };
    	for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
    	{
    		cout << a[i] << " ";
    	}
    	cout << endl;
    
    	//范围for
    	for (auto e : a)
    	{
    		cout << e << " ";
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述
    这里要注意的时e是a[i]的拷贝,改变e不会改变数组里面的元素,要改的话可以加引用

  • 相关阅读:
    初等数论总结
    2022年,谁在推动音视频产业的新拐点?
    Qt之延时总结
    代码随想录34|62.不同路径,63. 不同路径 II,343. 整数拆分
    Java的垃圾回收机制详解——从入门到出土,学不会接着来砍我!
    【Linux】ls命令
    CSS详细解析二
    【数据分析入门】Seaborn[散点图、条形图、计数图、热力图、箱型图、小提琴图]
    【Flink】Flink 中的时间和窗口之窗口(Window)
    那个写出最烂代码的程序员,不但进了Google,还财务自由了!
  • 原文地址:https://blog.csdn.net/qq_66314292/article/details/125416451