• C++入门(1)


    一、关键字

    C语言中只有32个关键字,C++有63个关键字,将近翻了一倍。
    在这里插入图片描述

    二、命名空间

    在编写代码的时候,会遇到定义的变量名和库中的函数名重复,出现命名冲突的情况。在C++中有变量名、函数名还有类名,这些都会存在全局域中,在大项目中很难避免命名重复的问题。为了解决这种问题,通过namespace关键字去命名一块空间,用于存放变量名、函数名还有类名。而这种空间通称为命名空间,命名空间中的定义的这些变量、函数或者类其实还在全局域(静态区)中。

    1.命名空间的使用

    首先需要定义一块命名空间

    #include
    namespace new1
    {
    	int a = 0;
    	int b = 10;
    }
    
    int main()
    {
    	//printf("%d\n", a);//错误用例,报错信息为a为定义
    	printf("%d\n", new1::a);//正确用例
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    为什么说定义的变量在全局中,但是不可以直接使用呢?举个例子,现实生活中拿东西,你得先看到物品在哪,才能去拿。如果说这个物品隐身了,你看不到它,自然无法拿到。namespace定义了new1的命名空间,这个空间在全局中,但是new1中的空间内容默认是不可见的。new1::a中的"::"是作用域限定符,左边是作用域名称(全局域就不用写),右边是成员名称。作用域限定符相当于帮你拿到你看不到空间中存在的物品。这样子可以避免命名冲突,只要存在的命名空间不一样,就没有问题。

    2.在一个工程中可以存在多个名字相同的命名空间

    #include
    namespace new1
    {
    	int a = 10;
    }
    namespace new1
    {
    	int b = 10;
    }
    
    int main()
    {
    	printf("%d\n", new1::a);
    	printf("%d\n", new1::b);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这因为编译器最后会对名字相同的命名空间进行合并。

    3.命名空间可以嵌套

    #include
    namespace A
    {
    	int test1 = 10;
    	namespace B
    	{
    		int test2 = 20;
    	}
    }
    
    int main()
    {
    	printf("%d\n", A::test1);
    	printf("%d\n", A::B::test2);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    命名空间嵌套定义,在使用时一层一层解开,如声明代码中的test2变量,A::B::test2可以看做A::(B::test2),和俄罗斯套娃一样。

    4.using剥掉命名空间隐身衣

    有人觉得自己就做个题,不想每次都用作用域限定符写。那可以直接用using将想要用的命名空间的隐身衣剥去,让它暴露在人们的视野里。

    #include
    namespace A
    {
    	int test1 = 10;
    	namespace B
    	{
    		int test2 = 20;
    	}
    }
    using namespace A;//将命名空间A的内容暴露在当前作用域内
    using namespace B;//将命名空间B的内容暴露在当前作用域内
    //如果只想要B中的内容,可以 using namespace A::B;
    int main()
    {
    	printf("%d\n", test1);
    	printf("%d\n", test2);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    有人又觉得自己只需要其中的某个变量或者函数等等,可以using A::test1,之后就可以直接使用test1变量了。个人推荐后一种方法,前一种虽然方便,但是潜在风险比较大。namespace本身就是降低命名冲突问题的,using可以让每个人更自由的去使用命名空间中的内容,namespace相当于主力军,using相当于辅助,大多数情况下,不会有人让辅助去当主力军的。

    三、输入输出

    #include
    using std::cin;
    using std::cout;
    using std::endl;
    
    int main()
    {
    	int a;
    	cin >> a;
    	cout << "hello world!"<<endl <<"hello doudou!"<<endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    cout是标准输出对象(控制台),cin是标准输入对象(键盘输入)。cin和cout后面的>> 和 << 分别是流提取运算符和流输入运算符,endl相当于换行。cin、cout、endl都在iostream库中的std命名空间中定义。正常情况头文件都有带.h,为什么C++的iostream库没有.h后缀,主要是和C中的iostream.h库做区分。可以看出iostream库中有命名空间std的存在,而C中没有命名空间这个概念,当然有些旧版的C++编译器仍然支持iostream.h格式。
    有人说cout、cin比printf和scanf慢,在早些时候这个差距会比较明显一些,但是随着机械部件不断迭代更新,这种差距基本上忽略不计。用哪种方法看个人习惯或者具体运用事例,哪种方便就用哪种。

    四、缺省参数

    缺省参数就是给函数参数给定一个默认数值,当调用函数时有传入数值,就正常进行传值操作,没有就采用默认值。
    缺省参数又分为全缺省参数和半缺省参数

    1.全缺省参数

    #include
    using namespace std;//测试用例就偷个懒,不建议做项目这样写
    //全缺省参数
    void f(int a = 1, int b = 2, int c = 3)
    {
    	cout << a << endl;
    	cout << b << endl;
    	cout << c << endl;
    }
    int main()
    {
    	f();
    	f(10);
    	f(10, 20);
    	f(10, 20, 40);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    顾名思义,就是所有参数都是缺省参数。调用可以传入数值,如果有传入数值,必须按顺序传。如上面代码中的f函数,不可以用f( , 20,);这样的方式调用函数,想要传入某个参数,其前面的所有参数都需要传入数值,后面的可以不用。

    2.半缺省参数

    半缺省参数是指函数有一个或者一个以上的缺省参数,且没有全部参数都为缺省参数。

    #include
    using namespace std;//测试用例就偷个懒,不建议做项目这样写
    //半缺省参数
    void f(int a, int b = 2, int c = 3)//半缺省参数函数
    {
    	cout << a << endl;
    	cout << b << endl;
    	cout << c << endl;
    }
    //void f(int a, int b, int c = 3)//半缺省参数函数
    //{
    //	cout << a << endl;
    //	cout << b << endl;
    //	cout << c << endl;
    //}
    int main()
    {
    	f();
    	f(10);
    	f(10, 20);
    	f(10, 20, 40);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    半缺省参数函数定义时,只能按顺序定义,函数定义的缺省参数后面必须都是缺省参数。半缺省参数函数调用方式和全缺省类似,没有缺省参数的地方依旧正常传数值,有缺省参数的地方,依旧是想要传入的参数,其前面的所有参数都需要传入数值,后面的可以不用。

    五、函数重载

    C++中支持同个作用域内可以声明定义多个名字相同功能相识的函数,当然是有规则的。现在就要讲讲什么时候可以定义名字相同的函数,且能被识别出来。主要三个方面,第一个是函数参数个数不同,第二个是函数参数类型不同,第三个是函数参数顺序不同。

    这三个规则通过代码的修饰展现:

    //1.参数类型不同
    int Add(int a, int b)
    {
    	cout << "int Add(int a, int b)" << endl;
    	return a + b;
    }
    double Add(double a, double b)
    {
    	cout << "double Add(double a, double b)" << endl;
    	return a + b;
    }
    
    //2.参数个数不同
    void Number(int a)
    {
    	cout << "void Number(int a)" << endl;
    }
    void Number(int a, int b)
    {
    	cout << "void Number(int a, int b)" << endl;
    }
    
    //3.参数顺序不同
    void Order(int a, char b)
    {
    	cout << "void Number(int a, char b)" << endl;
    }
    void Order(char b, int a)
    {
    	cout << "void Number(char b, int a)" << endl;
    }
    
    int main()
    {
    	Add(1, 2);
    	Add(1.1, 2.2);
    	Number(1);
    	Number(1, 2);
    	Order(1, 'a');
    	Order('a', 2);
    
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    这三个规则一定在什么时候都适用吗?真要细究不一定,这主要是第三个会有些问题,例如我定义一个函数中的参数类型都是一样的,就算参数的顺序不同,依旧会报错。为什么会这样?我们得去为什么可以实现函数重载。C语言中没有函数重载的概念,在C++中为什么有,因为编译器对于函数的命名规则不一样。这边先补充一个知识点,一个项目的文件中的程序是如何运行起来的。首先需要经过预编译,然后是编译,在之后是汇编,最后就是链接。预编译是处理一些预编译指令的,编译就是经过编译器生成一串串的汇编代码,汇编就是将这些汇编代码转化二进制指令,形成符号表。链接就是合并这个项目中的所有符号表。就是将这些这里不深讲,大致讲个过程,后面会单独出一章讲解这些过程中的细节。
    编译器负责的就是编译的过程。在编译过程中,C语言的函数是直接用函数名修饰的,也就是说两个同样函数名的函数,即使参数不一样,C语言也分辨不出来。Liunx下的C++编译器对于函数名的修饰规则是_Z+函数长度+函数名+参数类型首字母。可以看出同名函数想要区分得靠最后一个参数类型做区分,这里也可以看出为什么类型相同的参数,调换顺序也无法识别出来,因为类型相同无法区分。上面三条规则为什么能适用,哪里会存在问题,记住这条规则便可。

    六、引用

    引用就相当于是给一个人或则一个东西取了一个别名。就像棍子,有的人也叫它棒子,东西还是同一个,名字不同罢了。那引用也一样,就是给一个变量多了一个变量名称,地址都一样,不开辟新空间。

    1.引用的使用方法

    想使用,得先定义吧,那就先看一下如何定义引用。

    #include
    
    int main()
    {
    	int a = 10;
    	int& b = a;
    	printf("a = %d, b = %d\n", a, b);
    	printf("a = %p, b = %p\n", &a, &b);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    定义方法就是:类型 & 引用名 = 引用变量名;
    那有人就说了,我不初始化不行吗?我就定义一个引用不行吗?不行,一定要初始化,不然编译不过。语法规定好的,别去钻牛角尖。

    一个人有多个外号可以吗?当然可以,那引用也一样,一个变量可以有多个引用名,对引用名进行引用,本质上还是对同一个问题取外号,都是指向同一个变量。
    下面是代码验证:

    #include
    
    int main()
    {
    	int a = 10;
    	int& b = a;
    	int& c = a;
    	int& d = c;
    
    	printf("a = %d, b = %d, c = %d, d =%d\n", a, b, c, d);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    引用需要注意的一点,一旦对一个变量进行引用就不可以将这个引用名转到另一个变量上。例如我给a取了引用名为c,那c就只能是a的引用名,不可以把它变成b的引用名。所以,引用一旦完成,就会从一而终。和指针这个渣男不一样,可以换对象。

    2.常引用

    常引用就是通过const对引用进行修饰。
    下面是使用案例:

    #include
    
    int main()
    {
    	const int a = 10;
    	//int& b = a;//a为常量,权限不同报错
    	//int& b = 10;//10为常量,报错
    	const int& a1 = a;
    	const int& b = 10;
    
    	double c = 1.22;
    	//int& d = c;//报错,类型不同
    	const int& d = c;
    	printf("%f", d);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    上面举了几个典型例子,现在来讲解一下为了有些地方不行。常变量a是只能读不能修改的,但是主函数第二行的引用名b是既能读又能改的,两者权限不一样。权限可以缩小,但是不能放大。如果对常变量a取一个变量类型的别名b,那相当于是放大了a的权限,因为b是可以对a进行修改的,a和b就是同一个东西,所以不可以通过,合情合理。第三行也是同样的道理,10是常量,b是变量,常量的权限小于变量,不可以通过。
    那第四行和第五行为什么可以通过,就是大家权限相同。
    第八行不能通过是因为d和c的类型不一样,所以报错。当然有人就会说那第二行和第三行就不能是类型不一样吗?我只能说每个人的理解不一样,你就得可以都解释的通就行。
    第九行就是反驳点了,你说类型不一样就会报错,为什么第9行不报错?这就是我前面第二三行为什么要说是权限问题报错,变量为什么可以给常变量,就是权限大的可以缩小,变量的权限比常变量大,能通过合情合理。但是这里会有个问题,d是常变量只能看不能改,但是c是变量啊,它能改,这有问题,还是不对。确实有问题,但是这个问题有个巧妙的地方让这个问题消失了。当d给c做引用时,其实给的不是c,给的是c的整数,也就是说c是1.22,给到b的其实是1,这个1是一个临时变量的值。也就是说,c先将其整数值给到临时变量,再将这个临时变量给到d。那d和c是没有半毛钱关系的,后面修改c不会改变d的值,引用始终如一。

    3.为什么使用引用?

    有人觉得引用可以做到的事情,指针不一样行吗?确实如此,引用的产生是为了将一些指针能做的事情做的更方便,因为指针过于复杂,引用相当于一个平替,可以完成大多数指针可以完成的事情,用起来简单。像链表就不能用引用去完成,引用没有指针灵活。

    引用主要应用在两个地方,一个是函数参数,一个是函数返回值。我们先说使用方法,再谈为什么这样做。

    #include
    void Swap(int& a, int& b)
    {
    	int tmp = a;
    	a = b;
    	b = tmp;
    }
    
    int& A()
    {
    	int d = 30;
    	int& c = d;
    	return c;
    }
    int main()
    {
    	int a = 10;
    	int b = 20;
    	Swap(a, b);
    	printf("a = %d b = %d\n", a, b);
    	int c = A();
    	printf("c = %d\n", c);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    上面代码在有些环境下是可以通过的,这不代表代码没问题,先声明一点,上面代码是存在问题的。函数参数使用引用是没有问题的,可以很方便的使用,并且不会对参数进行拷贝,在数据量很大的情况下,可以有明显的效率提高。举个例子,我传一个10万字节的结构体,如果用传值调用,在函数调用建立函数栈帧时,函数得开辟一个10万字节的空间对这个结构体进行拷贝,这里存在时间的消耗是很明显的。

    函数返回值的代码是有问题的,下面就具体讲讲这个问题是什么。首先要明确的一点是,函数在调用时,会建立函数栈帧,函数结束后,函数栈帧会销毁。上面将函数A中的d变量作为返回值给了c,乍一看好像是这样。实际上,c是拿到了函数A中b变量空间的引用,换句话将,本质上c变量得到的是b的地址中的内容的引用名。这里面就存在隐患了,因为函数结束后函数栈帧是会销毁的,函数栈帧相当于是系统临时给函数分配的空间,一旦函数结束,这片空间就会回收,有的系统会对空间内容进行清理,有的系统不会,这就是为什么说有的环境下可以通过这个代码的原因。这时候就有人说了,那我知道了我的系统不会对销毁的函数空间进行清理,那我不就可以正常使用了。
    片面了,这也是为什么要说函数栈帧的原因,因为函数栈帧销毁后,空间会被系统回收,而这块回收的空间又在别的函数建立时被占用。函数栈帧销毁,空间是会被系统回收的,也就是说这个空间的内容不可以随意被访问,没有权限。能拿到只是没被检查出来,实际上就是违法访问空间。如果说,别的函数使用了这块空间,那c和一个会被随时会被更改的空间建立了引用关系,这就违背了作者的初心,c的值也会变幻莫测。
    关键点:函数返回值做引用正确使用方法是什么?返回对象出了作用域还在,就可以使用。如果空间归还给系统,就不推荐这样使用。

    如有不当之处,感谢各位大佬指正。

  • 相关阅读:
    金仓数据库KingbaseES客户端编程接口指南-ado.net(10. 预编译语句)
    Java 泛型概念与优势(一)
    【Java面试】TCP协议为什么要设计三次握手?
    2022软件测试3大发展趋势,看看你都知道吗?
    【分立元件】贴片电阻过电压故障机理
    ssm在线学习系统毕业设计-附源码211707
    MySQL-三大日志
    自动拉取推送代码到git仓库脚本
    文盘Rust -- 如何把配置文件打包到二进制文件里
    Node学习(一)-全局变量——global是node中最大的一个对象-可省略 & dirname-目录的绝对路径 & filename-文件的绝对路径
  • 原文地址:https://blog.csdn.net/qq_60185118/article/details/127901932