目录
在正式学习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);
文章浏览阅读5.1k次。 本人第一次写博客,菜鸟一只,也不大会用语言表述,写博客只是单纯记录下自己遇到的问题,并且记录下来以便日后使用的时候可以有个思路。如果能够帮助到别人就更好了。 现在公司的项目需要用vue做一个安卓app,需要实现app的自动更新功能。我的设计方案就是打开App先提交请求到后台,需要更新会返回下载地址。创建下载的代码: // 下载最新版本 JX_do..._h5+vue实现apk下载并自动打开
文章浏览阅读4.3k次。1.web.xml中加入以下配置信息. DisplayChart org.jfree.chart.servlet.DisplayChart DisplayChart /DisplayChart 2.jfreeChart.jsp<%@ page_jfreechart画图返回给浏览器</div>
文章浏览阅读489次。.imgclass { width:250px; height:250px; } .imgclass1 { width:210px; height:210px; } table tr td { width
文章浏览阅读2.4w次,点赞7次,收藏38次。WebView现在Android开发基本都会用到WebView,所以自己准备系统的整理下,供自己学习之用. 这是我的WebViewDemo的GitHub地址.是一个带有顶部进度条WebView的小Demo,可以去看下~~1.简介WebView是一个基于webkit引擎、展现web页面的控件。 Android的Webview在低版本和高版本采用了不同的webkit版本内核,4.4后直接使用了Chr_webview
文章浏览阅读1.5k次。图像二值化是图像分析与处理中最常见最重要的处理手段,二值处理方法也非常多。越精准的方法计算量也越大。一、threshold-全局固定阈值分割threshold(Image : Region : MinGray, MaxGray : )使用全局固定阈值分割图像,阈值从输入图像中选取灰度值g满足以下条件的像素点满足条件的图像的所有点作为一个区域返回。如果传递多个灰度值间隔(MinGray和MaxGray的元组),则每个间隔返回一个单独的区域。对于向量场图像,阈值不应用于灰度值,而是应用于向量_halcon histo_to_thresh
文章浏览阅读1.4k次,点赞31次,收藏16次。计算机最基本的处理数据的单元就是计算机的指令,同时一系列计算机指令的有序组合就构成了程序。下面是计算机系统常见的7条指令:假设该计算机指令系统的指令名(如 Store, Add 等),以及所涉及的数据X,Y,Z,P等。指令一:Input X;将当前数据储存到X单元中指令二:Output X;将X单元中的数据输出指令三:Add X Y Z;将X与Y中的数据相加并储存到Z中指令四:Sub X Y Z;将X与Y中的数据相减并储存到Z中指令五。_1.c 语言中对一些标识符规定了专门的用途,如int,if,while,case等,它们(能、不
文章浏览阅读4.7k次,点赞2次,收藏7次。Docker学习之安装docker-compose命令:采用Python-pip命令安装本机系统环境介绍Docker Compose简介使用Python-pip命令进行安装第一步:环境检查第二步:安装Python-pip第三步:安装docker-compose第四步:检查是否安装成功第五步:通过pip命令卸载docker-compose本机系统环境介绍Ubuntu系统环境介绍介绍Ubunt..._docker compose pip安装
文章浏览阅读99次。import requests,re,timeheader ={"Cookie":"登陆过账号后的cookie 必须填写","User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36"}#访问个人帐号下的贴吧主页..._贴吧签到获取tbs失败
文章浏览阅读5.2k次,点赞4次,收藏14次。C类型语言的各种编译器,比如(gcc clang)编译器有关的名词:GNU,GCC,CLANG,LLVM等编译器简单地说,编译器可以看作是一个语言翻译器。就像把中文翻译成英语一样,编译器可以把高级语言翻译成计算机能够执行的机器语言。这样看来,GCC可以算得上是一个精通多国语言的高级翻译官了。最简单的GCC使用指令如下所示:gcc hello.c -o helloGCC接受hel..._gcc llvm
文章浏览阅读306次,点赞2次,收藏4次。TDOA-GDOP计算背景TDOA定位是一种利用时间差进行定位的方法。通过测量信号到达监测站的时间,可以确定信号源的距离。利用信号源到各个监测站的距离(以监测站为中心,距离为半径作圆),就能确定信号的位置。一个目标点:E(x,y,z)E(x,y,z)E(x,y,z)四个测向站:S0(x0,y0,z0),S1(x1,y1,z1),S2(x2,y2,z2),S3(x3,y3,z3),S_0(x_0,y_0,z_0),S_1(x_1,y_1,z_1),S_2(x_2,y_2,z_2),S_3(x_3,y__tdoa gdop
文章浏览阅读2.8k次。先看下效果图……(o^^o)1. 可以使用:tree-props="{children: ‘children’, hasChildren: ‘hasChildren’}"直接实现表格的展开,可是这样就无法把icon单独放到一列,会直接放在第一例,就像官网实例一样,局限性比较大2.所以我使用自定义展开项expand,通过配置row-class-name来决定是否展示展开图标template部分: <el-table :data="tableData" :loading="loadi_更改表格展开列的icon
文章浏览阅读702次。求助linux下编译libssh2问题linux编译libssh2时问题如下,/usr/bin/ld: /opt/openssl/lib/libcrypto.a(hmac.o): relocation R_X86_64_32 against `a local symbol' can not be used when making a shared object; recompile with -f..._linux编译 libssh2-1.9.0 提示--with-crypto