目录
在正式学习C++之前,我们首先得认识了解我们学习的C++创始人,Bjarne Stroustrup(本贾尼·斯特劳斯特卢普),本贾尼博士在使用C语言的过程中发现了C语言有许多方面上使用的不便,于是在C语言的基础上创立了C++,也就是说,C++文件中可以编译C语言代码,而C语言文件中也可以编译C++代码,可以理解为C++继承了许多C语言的特点。
与C语言相比,C++新增了一些关键字,从C语言的32个关键字到C++的63个关键字,我们之前也了解过关键字的概念,此处就不详细讲解,大概列出关键字,以供参考,具体每个关键字在后面的学习中慢慢了解;
我们的C++祖师爷本贾尼博士在使用C语言的过程中,发现了我们在定义变量名或者函数名的时候经常会与库函数重名,比如如下代码;
#include <stdio.h>
int main()
{
int rand = 10;
printf("%d\n", rand);
return 0;
}
在编写C语言代码时,我们定义一个名叫rand的变量,但是库函数中也有一个叫rand的函数名,因此,在编译以上代码时,会发生编译错误。在项目工程的开发中,经常是以多人分工完成同一个项目,最后将所有人的项目代码总和,实现项目工程,而在总和过程中,经常会发生同名的情况,因此我们的祖师爷本贾尼在设计C++的时候,提出了命名空间这一该概念。
为了防止多个库将名字都放在全局命名空间(全局作用域)内引起的命名空间污染,命名空间提供了可控的机制,将全局命名空间进行了分割,其中每个命名空间都是一个作用域。
命名空间的定义有关键字+命名空间名+{}组成,具体定义如下;
namespace zhangsan
{
//声明或定义(命名空间成员)
int a;
char c;
void Swap(int* p1, int* p2);
}
以上代码定义了一个名叫zhangsan的命名空间,该命令空间有三个成员,分别声明了一个整型a,一个字符型c和一个函数Swap;
注意:命名空间不可定义在函数和类的内部
所谓可以不连续性也就是命名空间不必定义在同一块空间,我可以在这个文件定义zhangsan这个名字的命名空间,也可以在别的文件定义zhangsan这个名字的命名空间,编译器最后会将这些同名的命名空间合并成一个命名空间。
命名空间是可嵌套的,也就是我们平时说的套娃 ,具体如下代码;
namespace stu
{
namespace zhangsan
{
int a;
}
namespace lisi
{
int a;
}
}
以上代码在全局作用域内定义了一个叫stu的命名空间,该命名空间里定义了两个子命名空间,分别为zhangsan和lisi,这两个命名空间分别有自己的成员;
using声明语句一次只引入命名空间的一个成员
格式: using 命名空间名 :: 引入成员名
//std为标准库的命名空间名
//cout为其中的一个成员名
using std::cout;
以上代码意思可理解为将命名空间名为std中的成员cout引入全局命名空间(全局作用域)中来。
注:其中::(双冒号)为域操作符;
//引入标准库
#include <iostream>
//using声明
using std::cout;
using std::endl;
int main()
{
cout << "hello world" << endl;
return 0;
}
以上代码段首先引入了iostream标准库,暂时可将其理解为C语言中引入stdio库一样的作用,std为标准库中变量存在的命名空间的名字,我们接着通过using声明将命名空间名为std中的cout(输出对象)和endl(换行符)引入到全局命名空间(全局作用域)中,所以接下来我们在局部作用域main函数中使用了cout和endl。
注:cout暂时可理解为输出的一种手段,类似C语言中的printf;
using指示语句一次可引入一个命名空间的所有成员
格式:using namespace 命名空间名
using namespace std;
以上代码将命名空间名为std的所有成员都引入到了全局作用域(全局命名空间)中;
阅读以下代码,让你对命名空间有更深刻的认识;
#include <iostream>
namespace A
{
int i = 0;
int j = 1;
int k = 2;
}
int j = 15;
int main()
{
//将命名空间A中所有成员引入全局作用域(全局命名空间)中
using namespace A;
i++; //对命名空间i++
j++; // 错误 因为这里无法确定是对刚引入全局作用域中j++,还是对原本全局作用域中的j++
A::j++; // 正确 对命名空间A的那个j++
::j++; // 正确 对原本全局作用域中的j++
int k = 0;
k++; // 对上一行的k++
A::k++; // 对命名空间A中的k++
return 0;
}
在以上变量中,尤其是 j 变量更能让你深刻体会到 j 变量是确确实实的被引入全局作用域(全局命名空间)中,当 j 变量被引入全局作用域中,因为原本全局作用域有一个 j 变量, 因此在我们使用 j 变量时应该指出我们使用的是那个 j 变量,确保程序的严谨性;
注:其实在我们平常写代码的过程中还有第三种引入方法,如下
#includ <iostream>
int main()
{
//在我们使用的变量或函数名前加域作用符,并指定其来自的命名空间
std::cout << "hello C++" << std::endl;
}
以上代码使用的cout和endl是来自于std命名空间,我们可以在其语句的每个变量、对象或函数名前指定其命名空间来使用它;
一提到C++的输入与输出,我们就不得不提cout和cin了,本文讲解此处的目的主要是浅浅的教大家学会用这两个对象,没错这两个并不是函数,而是两个对象,此处不做深入讲解,此文主要带着大家入门C++。
该对象用于输出打印,功能类似于C语言中的printf,使用前需引入头文件 iostream,用法如下;
#include <iostream>
using std::cout;
using std::endl;
int main()
{
int a = 20;
cout << a << endl;
return 0;
}
既然要使用iostream里的成员,就必须将其引入作用域内,iostream的命名空间为std,因此使用前我们通过using声明引入了cout对象和endl(换行符),接着我们用流插入将a变量插入到cout对象中就可打印出结果;
注:
cin为C++的输入对象,功能类似于C语言中的scanf函数,使用前也许引用头文件iostream,其命名空间名为std,其具体用法如下;
#include <iostream>
using std::cin;
int main()
{
int a = 0;
cin >> a;
return 0;
}
通过以上代码,我们可以通过输入,对a进行赋值,cin的特点于cout类似,其中特别强调的是>>为流提取运算符;
通过调试我们确实发现a的值被我们修改为了188;
相比于C,缺省参数是C++特有的一种参数,该参数可以在函数调用函数时不传实参,而有一个默认的实参
#include <iostream>
using namespace std;
int func(int a = 1)
{
return a;
}
int main()
{
cout << func() << endl; // 输出1
cout << func(10) << endl; // 输出10
return 0;
}
在上面代码中,当我们不给func函数传参时,a的默认值便为1,传参时,传过去的实参将会代替缺省参数,此时a便为10;
void func1(int a = 1, int b = 2, int c = 3)
{
cout << "a" << a << endl;
cout << "b" << b << endl;
cout << "c" << c << endl;
}
void func2(int a, int b = 2, int c = 3)
{
cout << "a" << a << endl;
cout << "b" << b << endl;
cout << "c" << c << endl;
}
注:
函数重载是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。
以下我们用函数重载实现不同类型的相加函数;
int Add(int a, int b)
{
return a + b;
}
double Add(double a, double b)
{
return a + b;
}
int main()
{
Add(1, 3); // 调用第一个Add函数
Add(2.6, 5.7); // 调用第二个Add函数
return 0;
}
在调用Add函数时,编译器会通过形参找到对应的函数重载;
int Add(int a, int b)
{
return a + b;
}
int Add(int a, int b, int c)
{
return a + b + c;
}
函数参数个数不同可以构成重载;
int Add(int a, int b)
{
return a + b;
}
double Add(double a, double b)
{
return a + b;
}
函数参数类型不同也可以构成重载;
void func(int a, double b)
{
}
void func(double a, int b)
{
}
函数参数顺序不同构成重载;
注意:相同类型参数顺序不同不能构成重载,比如两个int型,先后顺序不同不能构成重载
问题:函数返回值不同是否能构成重载呢?
假设可以构成重载,有以下两个函数;
int func(int a, double b)
{
}
void func(double a, int b)
{
}
int main()
{
func();
return 0;
}
那么以上代码调用func函数时,会调用哪个函数呢?显而易见,这样的程序是有歧义的,调用函数时,编译器会通过参数来区分同名函数,而参数相同时,编译器也无法区分应该调用哪个函数了,因此返回值不同是不能构成函数重载的;
问:编译器是如何通过函数参数的不同来区分调用不同的函数呢?
实际上,在函数编译的过程中,编译器会对每个函数进行修饰,不同的编译器修饰规则也不同,接下来我会带着大家在linux的环境下,用gcc(C语言编译)和g++(C++编译)带着大家来观察函数重载的过程;
以下为gcc(C语言方式)编译后形成的汇编代码;
很显然,我们发现,用C语言方式编译后的汇编指令中的函数名并没有发生改变,函数名依然为原函数名;
接下来,我们用g++(C++方式)编译上述代码,以下为生成的汇编代码结果;
在以上汇编指令中,我们发现用C++编译放是进行编译生成的汇编指令中,函数的名字发生了变化,比如func1(int a, double b)中,func1变成了_Z5func1id,其中_Z为linux修饰函数特有前缀,5为函数名字符数,i为第一个参数类型int,d为第二个参数类型double,func2函数名依此类推
问:函数重载是否会影响程序的运行速度呢?
实际上,函数名的修饰是在编译过程中形成的,而只有在运行中的操作才会影响程序的运行速度,因此函数名的重载对运行速度并没有什么影响;
引用不是新定义了一个变量,而是为一个已存在的变量取了别名,编译器不会为引用重新开辟一块空间,而是和引用对象共同管理一块空间;
int main()
{
int a = 0;
int& ra = a; //定义一个引用类型变量
return 0;
}
以上代码定义了一个引用类型变量ra,当改变a的值时,ra的值也相应发生改变,因为他们共用一块内存空间;
int main()
{
int a = 0;
int& ra1; // err 未初始化
int& ra2 = a; // 正确
return 0;
}
int main()
{
int a = 0;
int& ra1 = a;
int& ra2 = a;
int& ra3 = a;
return 0;
}
ra1、ra2、ra3都是a变量的引用,也就是a的别名;
int main()
{
int a = 0;
int b = 2;
int& ra1 = a;
ra1 = b; //此时仅仅只是将b变量的值赋值给引用变量ra1,而不会让ra1称为b的引用变量
return 0;
}
所谓常引用指的是在普通引用对象前加上const修饰;
int main()
{
const int a = 10;
int& ra1 = a; //err 权限放大,有只读到可写可读
const int& ra2 = a; // 正确
int b = 20;
int& rb1 = b; // 正确
const int& ra2 = b; // 正确 权限缩小,由可读可写到只读
return 0;
}
第四行代码是错误的,因为原本的a变量是一个const修饰的变量,其只具有只读功能,而当赋值给他的引用ra1后,除了读以外还赋予ra写的功能,这本身并不合理,因此我们只能用一个常引用的变量来接收这个a变量;
总结:权限可以缩小和平移,不可以放大
void Swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
int& count()
{
static int count = 0;
count++;
return count;
}
注意:选择引用作返回值时一定要当心,比如以下代码;
int& Add(int a, int b)
{
int c = a + b;
return c;
}
此时明显是有问题的,当c变量以引用的方式返回时,出了作用域以后c变量被销毁,而返回的是c变量的引用,用的是同一块空间,可是这块空间在出作用域的时候被销毁了,这么做明显是不合理的;
引用与指针的不同点:
在C语言的学习中,我们学习过一种由宏定义的函数,但是宏定义的函数由种种缺陷,因此C++语言的设计中,定义了一种类似于宏函数的函数,取名为内联函数;内联函数是以关键字inline开头,如以下代码;
inline void Swap(int& a, int b)
{
int tmp = a;
a = b;
b = tmp;
}
int main()
{
int a = 10;
int b = 20;
Swap(a, b);
return 0;
}
接下来,我带着大家一起观察内联函数确实是取消调用了函数,而是在原处展开;首先我们要知道的是函数的调用在汇编语言中一般会有call语句,我的编译器(vs2022)默认在debug模式下不会对内联函数进行展开,因此需要进行如下设置;
完成了上述两步步骤后就可在debug模式下,观察内联函数的展开;
首先我们按F10进入调试模式(有些电脑需要按fn+f10),然后右键鼠标选择反汇编,我们在反汇编模式下进行调试。
在汇编代码中,我们发现了call指令,其为函数调用函数,说明了函数开辟了栈帧空间调用函数,接着我们加上关键字再进行如上调试。
在我们加入inline关键字后,我们并未在汇编指令中发现call指令,因此我们可以推断出在加入inline关键字后,函数并未采取开辟栈帧空间的方式进行调用,而是在原处展开。
1、inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。(其中的空间指的是程序空间)
2、inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。
3.、inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
C++使用auto修饰变量能自动识别变量类型,也就是说定义auto的变量接收赋值后,编译器会自动识别定义变量类型;
int main()
{
auto a = 10;
auto b = 'a';
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
return 0;
}
typeid是打印变量类型的函数,通过打印结果,我们确实发现auto能通过赋值的数据来自定义类型。
int main()
{
int a= 10;
//auto x; //err 错误写法,未初始化
//x = a;
auto y = a; //正确
return 0;
}
其实仔细想想也知道,如果不初始化,编译器又怎么知道auto应该是什么类型呢,要开多大空间呢?
int main()
{
int a = 10;
auto p1 = &a; //auto 与 auto* 是等价的写法,他们都表示一级指针
auto* p2 = &a;
cout << typeid(p1).name() << endl;
cout << typeid(p2).name() << endl;
auto ra1 = a; // auto 与 auto& 也是如此,他们都表示a的别名
auto& ra2 = a;
cout << typeid(ra1).name() << endl;
cout << typeid(ra2).name() << endl;
return 0;
}
许多初学者会误以为auto*是二级指针,其实并不是的,是否加*两者没有什么本质上的区别;
int main()
{
auto a = 4, b = 8;
auto a = 2, b = 4.9; // err auto只会对第一个变量进行类型推测
return 0;
}
在同一行中,auto只会对其第一个变量进行类型推测,默认认为后面的变量与第一个变量同类型,因此,在同一行auto语句中,只能存在一种类型;
注:除上面三点以外,还有以下两点要特别注意;
auto不能作为函数的形参,编译器无法对形参进行类型推导 ;
void test(auto a) // err
{
}
auto不能用于直接申请数组;
auto arr[] = { 1, 2, 4 }; // err
在C++11中,为我们提供了一种更简单的for语句,我们可以将这种新的for语句理解成为传统for语句的语法糖,具体用法如下;
for ( 变量 : 数组等) { 循环体 }
int main()
{
int arr[] = { 1,2,3,4,5,6 };
for (int e : arr)
{
cout << e << endl;
}
return 0;
}
以上for语句会自动将数组的每个元素依次赋值给变量e,实际上我们可以int e写成 auto e,这样写可以增加代码的通用性;但我们发现如果我们想将数组的每个元素乘以2并打印出来单纯的拿e*2并不能做到,因此又有以下写法;
int main()
{
int arr[] = { 1,2,3,4,5,6 };
for (auto& e : arr)
{
e *= 2;
cout << e << endl;
}
return 0;
}
使用引用就使得每个e变量都是数组每个元素的别名,共同维护同一块空间,因此可以进行*2操作;
在C++发展中,推出了一种新的空指针------nullptr,相当于NULL,实际上,这是C++填补C语言的坑,在传统的C头文件中,是这么对NULL进行定义的;
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
也就是说C++文件中,NULL会被定义为整型0,而非((void*)0),可能有人认为这并没有上面大碍,因为有时0确实就是我们所说的NULL,但是在某些情况下会出现很大的BUG,如下代码;
void f(int)
{
cout<<"f(int)"<<endl;
}
void f(void*)
{
cout<<"f(void*)"<<endl;
}
int main()
{
f(0);
f(NULL);
f((void*)NULL);
return 0;
}
在以上代码中,形成了函数重载,而在我们调用函数时,我们发现,我们传入整型时,调用的第一个函数,而我们传入NULL时,也是调用的第一个函数,这明显不符合我们的预期的,因此C++推出了nullptr,并将nullptr定义成((void*)0);
文章浏览阅读3.3k次,点赞7次,收藏39次。CPU 执行现行程序的过程中,出现某些急需处理的异常情况或特殊请求,CPU暂时中止现行程序,而转去对异常情况或特殊请求进行处理,处理完毕后再返回现行程序断点处,继续执行原程序。void 函数名(void) interrupt n using m {中断函数内容 //尽量精简 }编译器会把该函数转化为中断函数,表示中断源编号为n,中断源对应一个中断入口地址,而中断入口地址的内容为跳转指令,转入本函数。using m用于指定本函数内部使用的工作寄存器组,m取值为0~3。该修饰符可省略,由编译器自动分配。_51单片机中断篇
文章浏览阅读396次。项目经验(案例一)项目时间:2009-10 - 2009-12项目名称:中驰别克信息化管理整改完善项目描述:项目介绍一,建立中驰别克硬件档案(PC,服务器,网络设备,办公设备等)二,建立中驰别克软件档案(每台PC安装的软件,财务,HR,OA,专用系统等)三,能过建立的档案对中驰别克信息化办公环境优化(合理使用ADSL宽带资源,对域进行调整,对文件服务器进行优化,对共享打印机进行调整)四,优化完成后..._网络工程师项目经历
文章浏览阅读1k次,点赞31次,收藏30次。LVS:Linux Virtual Server,负载调度器,内核集成, 阿里的四层SLB(Server Load Balance)是基于LVS+keepalived实现。NATTUNDR优点端口转换WAN性能最好缺点性能瓶颈服务器支持隧道模式不支持跨网段真实服务器要求anyTunneling支持网络private(私网)LAN/WAN(私网/公网)LAN(私网)真实服务器数量High (100)High (100)真实服务器网关lvs内网地址。
文章浏览阅读899次。https://www.toutiao.com/a6713171323893318151/作者 | 黄小邪/言有三编辑 | 黄小邪/言有三图像预处理算法的好坏直接关系到后续图像处理的效果,如图像分割、目标识别、边缘提取等,为了获取高质量的数字图像,很多时候都需要对图像进行降噪处理,尽可能的保持原始信息完整性(即主要特征)的同时,又能够去除信号中无用的信息。并且,降噪还引出了一..._噪声很大的图片可以降噪吗
文章浏览阅读152次。目录谨慎地覆盖cloneCloneable接口并没有包含任何方法,那么它到底有什么作用呢?Object类中的clone()方法如何重写好一个clone()方法1.对于数组类型我可以采用clone()方法的递归2.如果对象是非数组,建议提供拷贝构造器(copy constructor)或者拷贝工厂(copy factory)3.如果为线程安全的类重写clone()方法4.如果为需要被继承的类重写clone()方法总结谨慎地覆盖cloneCloneable接口地目的是作为对象的一个mixin接口(详见第20_为继承设计类有两种选择,但无论选择其中的
文章浏览阅读958次,点赞21次,收藏24次。今天学长向大家分享一个毕业设计项目基于协同过滤的电影推荐系统项目运行效果:项目获取:https://gitee.com/assistant-a/project-sharing21世纪是信息化时代,随着信息技术和网络技术的发展,信息化已经渗透到人们日常生活的各个方面,人们可以随时随地浏览到海量信息,但是这些大量信息千差万别,需要费事费力的筛选、甄别自己喜欢或者感兴趣的数据。对网络电影服务来说,需要用到优秀的协同过滤推荐功能去辅助整个系统。系统基于Python技术,使用UML建模,采用Django框架组合进行设
文章浏览阅读614次。10G SFP+光模块被广泛应用于10G以太网中,在下一代移动网络、固定接入网、城域网、以及数据中心等领域非常常见。下面易天光通信(ETU-LINK)就为大家一一盘点下10G SFP+光模块都有哪些吧。一、10G SFP+双纤光模块10G SFP+双纤光模块是一种常规的光模块,有两个LC光纤接口,传输距离最远可达100公里,常用的10G SFP+双纤光模块有10G SFP+ SR、10G SFP+ LR,其中10G SFP+ SR的传输距离为300米,10G SFP+ LR的传输距离为10公里。_10g sfp+
文章浏览阅读239次。该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流项目运行环境配置:项目技术:Express框架 + Node.js+ Vue 等等组成,B/S模式 +Vscode管理+前后端分离等等。环境需要1.运行环境:最好是Nodejs最新版,我们在这个版本上开发的。其他版本理论上也可以。2.开发环境:Vscode或HbuilderX都可以。推荐HbuilderX;3.mysql环境:建议是用5.7版本均可4.硬件环境:windows 7/8/10 1G内存以上;_基于vue美食网站源码
文章浏览阅读62次。oldwain随便写@hexun链接:http://oldwain.blog.hexun.com/ ...
文章浏览阅读843次,点赞16次,收藏22次。用这个工具扫描其它网站时,要注意法律问题,同时也比较慢,所以我们以之前写的登录页面为例子扫描。_sqlmap拖库
文章浏览阅读1.5w次,点赞5次,收藏38次。Origin也能玩转图片的拼接组合排版谭编(华南师范大学学报编辑部,广州 510631)通常,我们利用Origin软件能非常快捷地绘制出一张单独的绘图。但是,我们在论文的撰写过程中,经常需要将多种科学实验图片(电镜图、示意图、曲线图等)组合在一张图片中。大多数人都是采用PPT、Adobe Illustrator、CorelDraw等软件对多种不同类型的图进行拼接的。那么,利用Origin软件能否实..._origin怎么把三个图做到一张图上
文章浏览阅读4.2k次,点赞4次,收藏51次。51单片机智能电风扇控制系统仿真设计( proteus仿真+程序+原理图+报告+讲解视频)仿真图proteus7.8及以上 程序编译器:keil 4/keil 5 编程语言:C语言 设计编号:S0042。_电风扇模拟控制系统设计