目录
一.C++的输入输出
#include<iostream>
using namespace std;
int main()
{
int arr;
printf("scanf,printf的输入输出\n");
scanf("%d", &arr);
printf("%d\n", arr);
cout << "cin,cout的输入输出"<<endl;
cin >> arr;
cout << arr;
}
与C语言输入输出第一个区别的点在于,C语言输入输出用的头文件是stdio.h,而C++输入输出用的头文件是iostream,但是iostream几乎包含了stdio.h的所有东西,所以就算不要stdio.h也可以使用printf和scanf
第二个区别在于C语言使用scanf与printf输入输出,在使用过程中需要提前写占位符,比如%d,而C++不需要使用%d占位符,直接输出就行,就算不同类型也可以直接一行输出
#include<iostream>
using namespace std;
int main()
{
int arr = 5;
double brr = 2.5;
cout << "cin,cout的输入输出"<<endl;
cout << arr<<endl<<brr;
}
对于换行,endl和\n都是换行,只是C++更喜欢endl
但是cout输出不是完全没有缺点的,cout和cin比printf和scanf要运行慢,cout
和 cin
是基于流对象的,而 printf
和 scanf
是基于缓冲区的。流对象在每次输出时都会进行一次系统调用,而缓冲区可以一次性输出多个字符,减少系统调用次数
比如说cout<<arr<<brr;和printf("%d%d",arr,brr),两个不同的变量输出,cout输出arr要进行一次系统调用,brr也要进行一次。而printf一次就可以全部输出了。不过这种速度的快慢的考虑只有在算法竞赛里才会去考虑,日常工作使用中不会考虑速度上的差异
同时在另一方面,C++的浮点数输出精度控制比C语言浮点数输出要麻烦许多,C语言输出浮点数只需要在%f那里进行操作就可以了,比如printf("%.2f", float_number);,而C++是通过很多精度函数来控制精度的,例如按5位有效位输出12.3 * 3.578的值:cout << setprecision(5) << 12.3 * 3.578 << endl;
流操纵算子 | 功能描述 |
---|---|
setbase(b) | 以进制基数 b 为输出整数值 |
setprecision(n) | 将浮点精度设置为 n |
setiosflags(long) | 设置特定的格式标志位 |
setw(n) | 按照 n 个字符来读或者写 |
setfill(ch) | 用 ch 填充空白字符 |
flush | 刷新 ostream 缓冲区 |
ends | 输出空字符 |
endl | 输出换行符并刷新 ostream 缓冲区 |
ws | 跳过空白字符(用于输入) |
相比之下在精度输出下要比C语言要麻烦很多,不过也不用特别去记函数,因为C++兼容C语言的,所以如果要按照精度输出那么直接使用C语言的精度输出就可以了,C++和C语言可以串着用的,哪个方便就用哪个
二.命名空间
命名空间的概念可以类比为“作用域”的一种扩展。命名空间提供了一种将代码组织成逻辑单元的方式,类似于将代码放置在一个独立的区域(空间)中,以避免命名冲突并提高代码的可读性和可维护性。但是,命名空间并不是开辟了一块独立的空间,命名空间主要是一种编译单元的组织方式,用于避免命名冲突、提高代码的可读性和可维护性。在程序执行时,并不会为命名空间中的内容分配额外的内存空间,所有的代码仍然存在于全局作用域中。
通俗一点就是在原有的一块地上圈地插牌子,表明这块地是我个人了,但是这并没有重新开垦土地,只是画圈(限制作用域)。
命名空间定义方法
命名空间需要namespace关键字,然后后面加上命名空间名,接上一对括号,括号里面可以定义各种变量甚至函数
如下
定义命名空间
namespace ttr
{
int arr = 0;
}
命名空间的作用
C++和C语言关键字很多,这些关键字在C语言里是做不了变量名的,要不然会报错。同样如果定义两个变量名相同的变量也会报重定义错误,但是如果把其中一个变量写进命名空间里,那么即使变量名相同或者和关键字相同也不会报错,例子如下
直接定义和关键字相同的变量名,直接报错
#include<iostream>
using namespace std;int printf = 0;
int main()
{
printf("haha");
}
定义在命名空间里,就算和关键字相同也不会发生冲突
#include<iostream>
using namespace std;namespace ttr//ttr为命名空间名,可以随便起名字
{
int printf = 0;
}int main()
{
printf("haha");
}
命名空间变量的使用
对于作用的变量,你不能直接像平常使用变量一样直接就可以拿来用,比如我在全局定义了int arr=5,那么我在main函数里,可以直接通过printf("%d",arr)就可以打印出来5了
但是如果命名空间里的变量直接打印用会怎么样
命名空间的变量直接用会直接报错,对于命名空间变量的使用,有三种使用方式
加命名空间名称及作用域限定符
需要作用域符号::才能访问到
使用using将命名空间中某个成员引入
使用命名空间using namespace展开引入
命名冲突
也许你已经注意到了using namespace std了,其实这也是一种命名空间展开 ,using namespace std
是 C++ 中的一个常见语句,它表示使用标准命名空间 std
中的所有成员,这样就不需要在代码中每次都写 std::
来限定命名空间。这样可以简化代码书写,但有时也可能会导致命名冲突或不明确性。比如C++的输出cout<<,如果没有using namespace std,那么就必须这样使用输出std::cout<<
什么叫命名冲突呢,命名空间有一个作用是可以定义与关键字相同的变量名,但是我上面的代码总共展开了两个命名空间,using namespace std标准命名空间和我自己定义的命名空间using namespace ttr。如果我在ttr里定义cin变量名(C++的输入),同时使用命名空间展开会怎么样呢
为什么会报cin不明确符号呢,变量的输出它会先去局部找有没有这个变量的初始化定义,然后去全局寻找。如果没有展开命名空间,那么它其实不会去命名空间里去找有没有这个变量名的定义。展开了就是允许直接访问命名空间的变量,在找完局部全局之后最后会去命名空间里寻找。但是问题来了,std里也有cin,自己定义的命名空间ttr也有cin,两者都命名空间展开了允许访问那么到底输出谁呢, 这就产生了歧义。所以一般很少使用命名空间展开,会产生歧义
命名空间变量的权限
对于变量我们都知道有全局和局部的变量,如果包含在函数里(包含main函数),那么变量就是局部变量。那么定义在命名空间里的变量是全局变量还是局部变量呢。
命名空间中的变量被视为全局变量。虽然这些变量被放置在命名空间中,但它们仍然存在于全局作用域中,所以可以在命名空间外部进行直接访问。如果命名空间和函数一样里面定义的变量属于局部变量,那么出来作用域就直接销毁了(出了括号就直接销毁),可是命名空间的变量并没有销毁,而是还可以访问打印它。但是命名空间也可以在里面定义函数,这个命名空间的函数里面的变量依旧是局部的。
也就是说命名空间其实不会影响改变变量是全局还是局部,去掉命名空间这个变量是局部的还是全局的,那么在命名空间里也依旧是什么属性
命名空间嵌套
命名空间里面也是可以定义另一个命名空间的,访问的时候需要两重作用域符号::
#include<iostream>
using namespace std;
namespace ttr//ttr为命名空间名,可以随便起名字
{
namespace ccr
{
int cin = 5;
}
}
using namespace ttr;
int main()
{
printf("%d", ttr::ccr::cin);
}
第一重域作用符找到的是最外层的命名空间,第二层域作用符找到的是里层的命名空间,这样才能访问到最里面的变量。
多个相同命名空间名的命名空间
如果在不同文件里或者在同一文件里定义了相同的命名空间名,不会像函数或者变量名一样报重定义,而是编译器最后会自动合并成一个命名空间
三.缺省参数
在C语言里调用函数给几个实参,那么在函数那边就得用几个形参来接收。C++里面引入了缺省参数这个概念,也就是你可以调用函数时只给一个参数或者完全不给值,对面函数里形参直接给出值。如果你的实参不给相应数量的值,比如你调用函数,实参只给了一个值,可是对面却有三个形参来接收,那么第一个参数是你具体给的值,而后两个形参也不会报错,而是输出你提前给的缺省值,如下代码所述,a接收的是实参的值15,而b和c没有给实参,所以打印出来的是缺省值
#include<iostream>
using namespace std;
void fun(int a, int b = 20, int c = 10)//a没有给缺省值,b给了缺省值20,c给了缺省值10
{
cout << a << " " << b << " " << c;
}
int main()
{
fun(15);
}
全部给缺省值的情况,一个实参都不给
#include<iostream>
using namespace std;
void fun(int a=15, int b = 20, int c = 10)//a没有给缺省值,b给了缺省值20,c给了缺省值10
{
cout << a << " " << b << " " << c;
}
int main()
{
fun();
}
值得注意的是,实参传递是从左往右传递的,比如fun(15,20)只给了两个实参值,对应函数里(a,b,c)的顺序,a会首先拿到15,b拿到20,而C只能去用缺省值了
#include<iostream>
using namespace std;
void fun(int a=15, int b = 20, int c = 10)//a没有给缺省值,b给了缺省值20,c给了缺省值10
{
cout <<"a的值:"<< a << " " <<"b的值:" << b << " " <<"C的值:" << c;
}
int main()
{
fun(15,20);
}
同样是上面的例子,fun(15,20)只给了两个实参值,对应函数里(a,b,c)的顺序,如果a和b都有缺省值,而c没有给缺省值会怎么样呢
#include<iostream>
using namespace std;
void fun(int a=15, int b = 20, int c)
{
cout <<"a的值:"<< a << " " <<"b的值:" << b << " " <<"C的值:" << c;
}
int main()
{
fun(15,20);
}
报了一个缺少形参3的默认实参,这是因为缺省参必须从右往左依次给出,右边是必须要有的。因为传参给形参接收顺序是从左往右给出的,实参的传递先给了a和b,c没有接收到值。如果先保证了左边有缺省值,右边没有缺省值 ,那么这种情况下会直接报错
同样也不能像下面一样中间不给缺省值,报了一个b缺少值
#include<iostream>
using namespace std;
void fun(int a=15, int b, int c=20)
{
cout <<"a的值:"<< a << " " <<"b的值:" << b << " " <<"C的值:" << c;
}
int main()
{
fun(15,20);
}
在C++中,当函数有多个参数,并且其中一些参数有缺省值时,这些有缺省值的参数必须是从右到左连续排列的。换句话说,你不能在有缺省值的参数和没有缺省值的参数之间交替。这是因为编译器在解析函数调用时,从左到右处理参数,如果遇到一个参数没有提供值,它会寻找该参数的缺省值。如果参数没有缺省值,编译器就不知道该如何处理,因此会报错
值得注意的是缺省参数不能在函数声明和定义中同时出现,如果同时出现,恰巧两个位置上提供的值不同,那么编译器就无法确定该用哪个缺省值
// 函数声明
void foo(int x, int y = 0);
// 函数定义
void foo(int x, int y) {
// 函数实现
}
最后说一句,缺省参数是C++里才有的,本来就是为了弥补C语言调用函数的局限而产生的。C语言里是用不了的,不要在.C文件里用缺省参数
四.函数重载
什么是函数重载呢,先看两段代码,分别是C语言和C++环境下的同一段代码
C语言环境
#include<stdio.h>
int add(int a, int b)
{
return a + b;
}
int add(double a,double b)
{
return a + b;
}
int main()
{
int a = 1; int b = 2;
add(a, b);
}
C++环境
#include<iostream>
using namespace std;
int add(int a, int b)
{
return a + b;
}
int add(double a, double b)
{
return a + b;
}
int main()
{
int a = 1; int b = 2;
cout << add(a, b);
}
为什么同样一段代码,都是定义了两个函数add,为什么C语言会报重复定义。而C++却是正常运行了,C++在这上面加了函数重载,重载直白一点就是重复使用的意思,允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数或类型或类型的顺序不同),常用来处理类似功能数据类型的不同的问题。比如浮点数加法和整数加法,他们会根据传递参数的类型去找对应的函数,浮点数加法就去浮点数函数加法,整型加法就去整型加法函数
#include<iostream>
using namespace std;
int add(int a, int b)
{
return a + b;
}
int add(double a, double b)
{
return a + b;
}
int main()
{
int a = 1; int b = 2;
double d = 2.5; double c = 3.5;
cout << add(a, b)<<endl;
cout << add(d,c);
}
如果写一个整数加浮点数会不会报错呢
include<iostream>
using namespace std;
int add(int a, int b)
{
return a + b;
}
int add(double a, double b)
{
return a + b;
}
int add(double a, int b)
{
return a + b;
}
int main()
{
int a = 1; int b = 2;
double d = 2.5; double c = 3.5;
cout << add(a, b)<<endl;
cout << add(d,c)<<endl;
cout << add(d, a);
}
整数加浮点数不会报错,只是有精度的缺失
也许你已经看出来了,重载函数其实就是用形参的类型,顺序,参数个数来进行区分的
为什么C++支持函数重载,而C语言不支持呢,在C语言里函数名是函数的地址,C语言会直接通过函数名去调用函数,找到函数的位置。但是如果有两个相同的函数名,C语言编译器就不知道会去找谁。而C++是通过修饰过的函数名去找函数位置,函数参数不同的类型修饰的规则就不同。这样就会与相同函数名的函数区分开来
windows下函数修饰规则
linux环境下示例
五.引用
引用的基本用法
引用是给变量起一个别名,给已经存在变量起另一个名字,本质上还是一个东西,是占用同一快空间,不会另外开辟空间
引用标志符也是&,类型&引用变量名(对象名)=引用实体
#include<iostream>
using namespace std;
int main()
{
int arr = 5;
int& brr = arr;
cout << brr << endl;
cout << arr << endl;
}
结果一致,因为本来就是同一个东西
打印两者的地址可以看出来两者指向的是同一块空间
值得注意的是引用在定义时必须初始化,一个变量可以有多个引用,但是引用一旦引用一个实体就不能再引用另一个实体
常引用
图一
图二
这行代码 int& brr = 5;
是错误的,因为不能将一个非常量整数值绑定到一个引用上。引用必须引用一个对象,并且这个对象在引用声明的时候必须已经存在。如果你想要创建一个对整数的引用,你需要先有一个整数对象,然后创建对这个对象的引用。或者如图二一样加个const也可以输出
如果我一开始定义const int arr=5;然后引用int &brr=arr会发生什么,或者反过来int arr=5,然后用const变量引用arr,const &int brr=arr又会发生什么
const int arr=5;然后引用int &brr=arr是因为权限的放大,引用前是只能读不能写,但是起别名却定义的int是只能写又能读了,对brr的修改会影响arr,但是arr是不能修改的,所以会报错
int arr=5,然后用const变量引用arr是权限的缩小,这种是允许的,原变量既能读又能写,const引用起别名限制了不能修改。但是arr原变量修改还是能影响到brr的值的
函数传值传参 传指针传参 传引用传参
传值传参
#include<iostream>
using namespace std;
void fun(int a, int b)
{
a = 15;
b = 10;
}
int main()
{
int a = 5; int b = 10;
fun(a, b);
cout << a << " " << b;
}
传值传参给函数,在函数里修改a b的值,然后打印依旧是原来没有调函数之前的值。传值传参形参是实参的一份临时改变,他们不是同一块空间,而是另外开了一片空间存一样的值而已。传值传参的变量出了函数作用域就直接销毁了,所以修改不会成功
指针传参
#include<iostream>
using namespace std;
void fun(int* a, int* b)//传的是地址,所以用指针接收
{
*a = 15;//解引用修改a
*b = 10;//解引用修改b
}
int main()
{
int a = 5; int b = 10;
fun(&a, &b);
cout << a << " " << b;
}
把变量的地址取出来用指针接收,通过指针找到a和b在内存中的位置,这样修改是直接对存a和b的那片空间进行修改,所以能修改成功。
引用传参
#include<iostream>
using namespace std;
void fun(int& a, int& b)//引用起别名
{
a = 15;
b = 10;
}
int main()
{
int a = 5; int b = 10;
fun(a, b);
cout << a << " " << b;
}
这样传值过去,用引用接收,实际上是给实参起了一个小名(形参名),本质上是同一个东西,所以对形参修改本质上是直接对实参进行修改
引用实际上是为了弥补C语言指针的难用而搞出来了,通过引用直接修改就不用去考虑究竟是一级指针还是二级指针还是直接传值了,简化了操作
函数传值值返回和传引用返回
传值返回案例
传引用返回案例
为什么传引用返回答案这么奇怪,在add
函数中,局部变量c
在每次函数调用时都会被创建和销毁。由于c
是局部于add
函数的,因此每次函数返回时,c
所占据的内存空间可能会被后续函数调用或其他程序活动所覆盖。arr
和brr
都是悬垂引用,因为它们都引用了在add
函数返回后不再存在的局部变量。尝试通过arr
和brr
访问这些值将导致未定义行为。
未定义行为意味着程序的输出可能是不可预测的,它可能取决于多种因素,包括编译器、编译器优化设置、内存布局和程序的其他部分。有时,程序可能会偶然输出看似合理的值,但这并不意味着代码是正确的或可移植的
六.内联函数
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧空间,用以提升程序运行的效率。
内联函数和宏都可以用来实现代码的快速执行,但是它们有一些重要的区别:
- 内联函数是由编译器处理的,它会在每个调用的地方直接将函数的代码插入,而宏是在预处理阶段进行简单的文本替换。
- 内联函数可以进行类型检查和参数传递,而宏不能。宏只是简单的文本替换,不会进行类型检查。
- 内联函数可以避免一些宏的问题,例如宏定义的变量可能会引起作用域问题,而内联函数则不会。
- 内联函数通常更安全,因为它会进行类型检查和参数传递,而宏可能会导致一些潜在的错误。
- 内联函数可以提高代码的可读性和维护性,因为它更像是一个正常的函数,而宏可能会使代码更加难以理解。
不加内联标准函数调用反汇编情况
加了内联标志内联函数反汇编情况
call是调函数的意思,从上图来看,加了inline符号后少了调函数, eax是x86架构中的一个寄存器,用于存储数据和地址。eax存了数据,直接将上一个数据加到下一个数据上,实际上就是直接完成了加法Add函数的运算,因为我函数定义的就是left+right,所以它就直接运算了
inline是一种空间换时间的做法,因为直接在代码中间展开了函数,所以目标文件会变大,但是少了函数调用那一步骤,提高了程序运行效率
inline对于编译器来说只是一种建议,有些时候编译器会忽略inline特性,内联函数用于优化规模较小,流程直接,频繁调用的函数
值得注意的是定义内联函数不用声明定义分离,分离会导致链接错误,因为内联函数展开,就没有函数地址了,链接就会找不到