赞
踩
前言:在有C语言的基础上学习C++会比较容易上手,所以建议先学完C语言的基础
目录
一个程序的运行过程,程序从源代码到可执行文件必须经历四个阶段
C++中的命名空间(Namespace)是一个将标识符(如类名、函数名、变量名等)封装在其中的作用域。这主要是为了解决在大型项目中可能出现的标识符冲突问题。不同的库或模块可能使用相同的类名或函数名,如果不加区分,就会导致编译错误或不可预期的行为。
举生活中的一个例子:不同的国家有不同的城市和街道名。例如,在美国和中国都可能有名为“北京”的地方,但显然它们是完全不同的。如果我们不加区分地谈论“北京”,可能会引起混淆。但是,如果我们说“中国的北京”或“美国的某个地方叫北京”,那么就可以清楚地知道我们指的是哪个“北京”。
定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{ } 即可,{ } 中即为命名空间的成员,这和我们学的C语言中的结构体的定义类似,{ } 后面不需分号结尾
- //1,普通命名空间的定义
- namespace N1 // N1为命名空间的名称
- {
- //里面可以定义变量
- int a = 0;
- int b = 0;
- //也可以定义函数
- int Add(int left, int right)
- {
- return left + right;
- }
- }
- int main()
- {
- return 0;
- }

- //2,嵌套命名空间的定义
- namespace N1
- {
- int a = 0;
- int b = 0;
- namespace N2 // N2命名空间定义在N1命名空间的内部
- {
- void Swap(int* p1, int* p2)
- {
- int tmp = *p1;
- *p1 = *p2;
- *p2 = tmp;
- }
- }
- }
- int main()
- {
- return 0;
- }

命名空间的成员如何使用呢,如果直接使用命名空间的成员,编译器会直接报错
接下来就来看看命名空间是如何使用的
- namespace N
- {
- int a = 10;
- int b = 20;
- int Add(int left, int right)
- {
- return left + right;
- }
- int Sub(int left, int right)
- {
- return left - right;
- }
- }
- int main()
- {
- printf("%d\n", N::a); // 加命名空间名称及作用域限定符(::)
- printf("%d\n", N::b);
- return 0;
- }

- namespace N
- {
- int a = 10;
- int b = 20;
- int Add(int left, int right)
- {
- return left + right;
- }
- int Sub(int left, int right)
- {
- return left - right;
- }
- }
- using N::a; // 使用using将命名空间的成员引入
- using N::Add; // 使用using将命名空间的成员引入
- int main()
- {
- printf("%d\n", a);
- int a = 10, b = 20;
- int c = Add(a, b);
- printf("%d\n", c);
- return 0;
- }

- namespace N
- {
- int a = 10;
- int b = 20;
- int Add(int left, int right)
- {
- return left + right;
- }
- int Sub(int left, int right)
- {
- return left - right;
- }
- }
- using namespace N; // 使用using namespace 将命名空间N引入
- int main()
- {
- printf("%d\n", a);
- printf("%d\n", b);
- return 0;
- }

在我们项目中我们可能会定义变量就会和命名空间里面变量发生冲突,但是日常练习中,我们不在乎与库名冲突,也就是我们日常练习中直接使用std这个命名空间,即using namespace std,但是不要和库中的命名一样就可以避免冲突
以下代码就和命名空间里面的名称发生了冲突
为了避免这种情况,我们可以有两种办法:
1,不要使用和命名空间中命名一样的名称
2,不要使用 using namespace std
- #include <iostream>
- using namespace std;
- int main()
- {
- int cout = 10; // 与std命名空间中的成员发生了冲突,
- cout << cout << endl;
- }
1,不要使用和命名空间中命名一样的名称
2,不要使用 using namespace std
每次写 std 比较麻烦,所有可以这样定义,但是这样定义之后就不能定义名为 cout 和 endl 变量了,常用的库里的一些对象可以展现出来,工程项目中通常使用这种写法定义命名空间
1.使用 cout 标准输出(控制台)和 cin 标准输入(键盘)时,必须包含头文件以及 std 标准命名空间。
注意: 早期标准库将所有功能在全局域中实现,声明在,h后级的头文件中,使用时只需包含对应头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确便用命名空间,规定C++头文件不带.h;
旧编译器(vc6.0)中还支持 <iostream.h> 格式,后续编译器已不支持,因此推荐使用<iostream> + std的方式
2.使用C++输入输出更方便,不需增加数据格式控制,比如:整形--%d,字符--%c,会根据数据的类型自动识别
endl会自动在末尾添加一个 '\n' 换行
缺省参数不是没有参数,而是有参数而有初始值,名字叫缺省参数
缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。通俗来说就是一个备胎,如果我们实参没有传参,就使用缺省参数,如果实参传了参数就不使用缺省参数
形参全部带有缺省参数(全部形参都有初始值),实参传参都是依次从左往右传参
部分形参带有缺省参数,缺省必须从右往左连续缺省,中间不能间隔缺省,如果没有缺省,必须传实参
注意:
1.半缺省参数必须从右往左依次来给出,不能间隔着给
2.缺省参数不能在函数声明和定义中同时出现
缺省参数只在声明时(或在首次定义时)指定,并且一旦为某个参数指定了缺省值,该参数之后的所有参数也必须有缺省值。
函数的声明和定义通常是分开的,但缺省参数只能在声明时(或在首次定义时)指定。这意味着你不能在声明时指定一组缺省参数,然后在定义时指定另一组不同的缺省参数
3,缺省值必须是常量或者全局变量
a.h
a.cpp 定义的时候不能再写缺省参数
Test.cpp
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 顺序)必须不同,常用来处理实现功能类似数据类型不同的问题
函数重载的意思就相对于一词多义,一个函数有多个意思,函数名相同实现功能类似数据类型不同的问题
参数的类型不同、顺序不同、个数不同,满足其中一个即可,返回值没有要求
以下Add函数就构成重载,对返回值没有要求
- #include <iostream>
- int Add(int a, int b)
- {
- return a + b;
- }
- double Add(double a, double b)
- {
- return a + b;
- }
- long Add(long a, long b)
- {
- return a + b;
- }
- int main()
- {
- return 0;
- }

函数重载可以根据所传的类型自动匹配
- #include <iostream>
- using namespace std;
- void Func1(int i, char c)
- {
- cout << i << " " << c << endl;
- }
- void Func1(char c, int i)
- {
- cout << c << " " << i << endl;
- }
- void Func1()
- {
- cout << 20 << endl;
- }
- int main()
- {
- Func1(10, 'a'); //自动对应函数一一匹配
- Func1('a', 20);
- Func1();
- return 0;
- }

1,一个程序的运行过程,程序从源代码到可执行文件必须经历四个阶段
2,Linux环境下的命名规则(C/C++),主要因为Linux环境下查看比较简洁3,C++的命名规则
以这个三个文件为例:list.h list.c test.c
预处理:头文件的展开、宏替换、条件编译、去掉注释 -> list.i test.i
编译:检查语法错误,将预处理后的源代码生成为汇编代码 -> list.s test.s
汇编:汇编代码转换成二进制的机器码 -> list.o test.o
链接:多个目标文件的文件链接成一个可执行文件
list.h
list.c
test.c
很明显的是,C语言不支持函数重载,编译出错
如果把那段Add重载的函数注释掉,可以发现编译通过,说明C语言不支持函数重载
可以发现C++支持函数重载,而且兼容C语言
在Linux环境下查看汇编代码:objdump –S 可执行文件 => 查看汇编代码
C语言编译后的结果,C语言中符号表用的名称就是原来函数名的名称(linux环境下是原函数名)
符号表主要在编译阶段生成,并在整个编译和链接过程中被使用。函数调用指令(如call
指令)在编译阶段被识别并准备,C语言生成的符号表函数名是原函数名(linux环境下是原函数名)
在Linux环境下查看汇编代码:objdump –S 可执行文件 => 查看汇编代码
通过下面我们可以看出 gcc 的函数修饰后名字不变。而g++的函数修饰后变成【_Z+函数长度+函数名+类型首字母】。g++符号表里面的名称是和类型名关联起来的
在有两个add的情况下C++可以编译通过,执行以下代码查看汇编代码,汇编代码中的符号表使用了名称修饰,(Linux 下的命名比较好理解)
综上所述:这就是为什么C语言不支持函数重载,链接了以后目标文件里,生成符号表里面的函数名就是原来的函数名(Linux环境下是原函数名),同名函数去找的时候,链接的时候就会冲突
而C++里面使用的是函数名修饰规则(个数,顺序,类型不同,所以不可能存在相同的情况),修饰后的函数名,链接之后的名称不会相同,不会发生冲突,从而支持了函数重载
注意点: 有了函数重载,我们知道了函数如何重载的,接下来可能会误认为以下代码构成重载
1,由于C++和C的函数名修饰规则不同,所以如果C++代码直接调用C语言的库函数或者C++代码被C代码调用,链接器就会找不到对应的函数定义,导致链接错误。
2,为了解决这个问题,C++引入了
extern "C"
。当使用extern "C"
时,编译器会将随后的代码块中的函数和变量按照C语言的链接约定来处理,即不进行函数名修饰。这样,C++代码就可以正确地调用C语言的库函数,或者C代码也可以正确地调用C++代码导出的函数。3,使用了extern "C" 之后就不支持函数重载了,就是使用一个C++库的时候,C/C++程序两者都需要用这个库,让C++的程序符号表用C的规则去找
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
变量是什么类型,引用也是什么类型,如果int& ra = a; ra 的类型不是 int&, 该类型还是int
1,引用必须在定义的时候初始化
2,一个变量可以有多个引用
3,一旦引用了其它实体,就不能引用其它实体
1,引用必须在定义的时候初始化
2,一个变量可以有多个引用
3,一旦引用了其它实体,就不能引用其它实体
它指的是一个被声明为指向常量的引用。这意味着通过这个引用,你不能修改所引用的对象的值。引用取别名时候,变量访问的权限可以缩小,但是不能放大
指针也存在这种权限的问题,这样我们可以引出一个问题,怎么区分指针常量和常量指针
指针常量:指针所指向的空间不能改变,指针解引用的内容可以改变,
常量指针:指针解引用的内容不能改变,指针的空间可以改变
语法格式:int * const p = &a;
特点:指针的指向不可以改,指针指向的值可以改
*p = 20; // 正确,指向的值可以改
p = &b; // 错误,指针指向不可以改
语法格式:const int * p = &a;
特点:指针的指向可以修改,但是指针指向的值不可以修改
*p = 20; //错误,指针指向的值不可以修改
p = &b; //正确,指针指向可以改
总结:
1,我们不需要管什么指针常量和常量指针,直接看const的修饰位置,先看图。接下来看第二点,区分指针常量和常量指针
2,如果要区别指针常量还是常量指针,我们把这个名字拆成两个一个叫指针,一个叫常量
const修饰谁,指针/常量,名字就在前面开始
假设我们定义:指针:就是地址,常量:就是解引用的值
如下图:①②const 修饰的分别是 *p1、*p2,即const修饰的是常量,所以叫常量指针
③ const 修饰的 p3, 即 const 修饰的是指针,所以叫指针常量。
以交换两个变量的函数Swap为例,我们知道在使用值传递的过程中,不会发生交换,因为Swap参数接收的到的形参会创建一份临时拷贝,在Swap作用域里面交换之后,出了作用域变量就销毁,里面的改变不会影响外面
所以我们一般使用指针传参,而这里可以使用引用传参,因为引用就是给变量取别名,传过去都是同一块空间的,没有创建额外的空间
临时变量具有常性,const 影响变量的修改权限,static 影响的是变量的生命周期
权限的放大和缩小对赋值没有影响
传引用返回
第二次把返回值改了,说明如果c是局部变量,引用返回是不安全的,因为c是局部,出来作用域就销毁,而ret变量还引用这这块空间
Add1
函数试图返回一个局部变量的引用,这是非常不安全的,在某些情况下它可能看起来能够“正常工作”。问题在于,当Add1
函数返回时,局部变量c
的生命周期就结束了,这意味着它所占用的内存空间可能会被释放或重用于其他目的。然而,你仍然持有一个指向这块已经无效内存的引用ret
。在这种情况下,访问
ret
(即尝试读取或写入这块内存)是未定义行为。未定义行为意味着程序的行为是不确定的,它可能在某些情况下看起来能够正常工作,但在其他情况下可能会导致崩溃。解决方法:1,传值返回
2,给变量加上 static,出来作用域也不会销毁
为什么下面代码不是7,而是3呢,因为我们都知道 static修饰的代码只会执行一次,也就是第一次进去的时候初始一次,后面就不在执行这行代码
如果想结果变成7可以这样修改
综上所述:
1,如果返回的变量出了作用域还存在就可以使用引用返回,如果返回的变量出了作用域就销毁,引用返回是不安全的
2,引用返回的好处是什么?
少创建一个临时变量,提高效率程序的效率,全局变量,静态变量可以使用引用返回
3,引用和指针的区别
I, 引用概念上定义一个变量的别名,指针存储一个变量地址。
II,引用一在定义时必须初始化,指针没有要求
III,引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向 任何一个同类型实体
IV,没有NULL引用,但有NULL指针
V,在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
VI,引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
VII,有多级指针,但是没有多级引用
VIII,访问实体方式不同,指针需要显式解引用,引用编译器自己处理
IX,引用比指针使用起来相对更安全
以 inline 修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销内联函数提升程序运行的效率。
就相当于宏定义的函数类似
内联适用于小函数,小于20行,递归得等都不适用内联函数,内联函数没有地址,在调用的地方就会展开
1,inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有代码比较长/递归的函数不适宜使用作为内联函数。
2,inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体代码比较长/递归等
等,编译器优化时会忽略掉内联。
3,inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会
C++11中,auto全新的含义为: auto不再是一个存储类型指示符,而是作为一个新的类型
指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
auto不能函数的参数
auto不能直接用来声明数组
语法:for(auto 变量 : 函数名)
为什么下面的值没有改变,因为这是通过值传递的,这种方式会创建容器或数组中每个元素的副本,并将其赋值给 e。因此,对e所做的任何修改都不会影响容器或数组中的原始元素。
所以我们要修改里面的值可以加引用
通过引用: 这种方式会创建对容器或数组中每个元素的引用,并将其赋值给e。这意味着e实际上是原始元素的别名,所以对e所做的任何修改都会直接反映到容器或数组中的原始元素上。
范围for的使用条件
传参之后会退化成指针
在C++98中,
字面常量0既可以是一个整形数字,也可以是无类型的指针 (void*) 常量,但是编译器默认情况下
将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。
注意:
1.在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
2.在C++11中,sizeof(nullptr)与 sizeof((void*)0)所占的字节数相同。
3.为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。