发布时间:2023-03-11 13:00
目录
1、前言:
1.1、什么是C++:
1.2、C++的发展史:
2、C++关键字(C++98):
3、命名空间(namespace):
3.1、命名空间定义:
3.1.1、普通的命名空间:
3.1.2、命名空间的嵌套定义:
3.1.3、命名空间的引入:
3.2、命名空间的使用:
4、C++输入&输出:
5、缺省参数:
5.1、缺省参数概念:
5.2、缺省参数分类:
5.2.1、全缺省参数 :
5.2.2、半缺省参数 :
5.3、缺省参数的意义:
1979年,贝尔实验室的本贾尼等人试图分析unix内核的时候,试图将内核模块化,于是在C语言的基础上进行扩展,增加了类的机制,完成了一个可以运行的预处理程序,称之为C with classes、语言的发展就像是练功打怪升级一样,也是逐步递进,由浅入深的过程,我们先来看下C++的历史版本、
#define _CRT_SECURE_NO_WARNINGS 1
#include
using namespace std;
//一、
struct ListNode1
{
struct ListNode1* next;
int val;
};
//上述程序在C和C++下均可编译成功,因为C++兼容大部分C程序、
//二、
struct ListNode2
{
ListNode2* next;
int val;
};
//上述程序只能在C++下编译成功,在C下编译不成功,因为,在C++中,结构体已经升级成为类,此时可以直接把ListNode当做类名去使用即可,所以不会报错
//C++对于结构体又添加了一些新的用法、
//注意上述例子和命名空间中当变量和结构体名字重复时,想得到变量和结构体类型具有不同的方法的情况是不一样的,注意区分、
int main()
{
return 0;
}
//也会存在只有C能够通过,C++不能过的情况,虽然常说C++兼容C,但并不是百分百兼容,能兼容%98-99左右,所以还会有一些程序只能在C下跑的通,但是在C++下跑不通的情况、
//#define _CRT_SECURE_NO_WARNINGS 1
//#include //类似于在C语言中的#incluede
两者都是用来和控制台进行输入和输出的,是IO流,对流进行输入和输出的、
在C++中通常包头文件,但是也可以包头文件,都是可以的,只不过前者通常使用在C++中,后者通常使用在C中、
NULL是一个宏,并不是关键字、
在C/C++ 中, 变量、函数和后面要学到的类都是大量存在的 , 这些变量、函数和类的名称若都存在于全局作用域中 , 可能会导致很多冲突 ,使用 命名空间 的目的是对标识符的名称进行本地化 ,以 避免 命名冲突或名字污染 , namespace关键字的出现就是针对这种问题的、
命名冲突举例:
不管在C还是C++下均会出现上述问题,原因是因为, 在头文件
注意:
在C语言中,系统自带的头文件只有一种形式,即均自带后缀,并且后缀均为.h,不能修改和删除其后缀,即,属于系统自带头文件并且带后缀,则不存在命名空间,作用域默认是全局域,但是,自己写的头文件,不能去掉后缀,若在创建头文件时不写后缀的话,则会默认后缀为.h,但是可以在不去掉后缀的前提下,修改后缀(除 .c、.cpp、.C、.CPP、.cPP、.Cpp等外都是可以的,windows系统不区分大小写,Linux系统下会进行区分),若是自己写的头文件,具体有没有命名空间,要看自己写的头文件是如何实现的、
在C++中,系统自带的头文件有两种形式,并且后缀是.h的头文件,第二种就是不带后缀的头文件,第二种不带后缀的头文件主,第一种就是带后缀要由,本身就不带后缀的头文件,比如:iostream,ostream等等,和 由本身带后缀.h的头文件删除后缀后在头文件前面加c变化成不带后缀的头文件,比如cstdio,cmath等等,这两种组成,,第一种系统自带的头文件并且带后缀,且后缀只能是.h,不可删除和修改后缀,则不含有命名空间,默认在全局域,第二种系统自带的头文件但不带后缀,则具有命名空间std,默认在命名空间域std中,若要是自己写的头文件,也不能去掉后缀,若在创建头文件时不写后缀的话,则会默认后缀为.h,但是可以在不去掉后缀的前提下,修改后缀(除 .c、.cpp、.C、.CPP、.cPP、.Cpp等外都是可以的,windows系统不区分大小写,Linux系统下会进行区分),若是自己写的头文件,具体有没有命名空间,要看自己写的头文件是如何实现的、
在C++中,头文件可写成stdio.h和cstdio两种形式,若把前者改写成后者的话,就相当于自动加上了一个命名空间std,所以说在C++中两种形式之间可以进行切换,但同时,他们的作用域也在不断地改变、
其次,由于函数不能嵌套定义,只能嵌套调用的原因,故所有的函数均可认为在全局域中,包括main函数,即所有的函数都是全局函数,不存在局部函数的概念,而变量既可能是局部变量,也可能是全局变量,所以,在上述例题中,不管是在C下还是在C++下,由于头文件stdlib.h是系统自带的头文件,并其还带有不可删除和修改的.h后缀,故默认该头文件的作用域在全局域中,即默认该头文件中的所有内容均在全局域中,而且,该头文件包括库函数rand的定义,所以在该头文件下包含了一个全局函数rand的定义,若再定义一个全局变量rand,则在相同的作用域内出现了函数与变量同名的情况,这就是所谓的命名冲突,上述情况,不管在C还是C++下均会出现错误,但是,目前而言,C语言没有办法解决这个问题,只能修改所定义变量的变量名,重新选一个新的变量名,而在C++下可以通过命名空间的方式来解决这个问题,即,可以把自己定义的全局变量rand放在一个命名空间内,这样即使与头文件stdlib.h中所包含的库函数rand重名也是可以的, 因为两者不在同一个作用域内、
拓展:
已知,库函数rand包含在头文件stdlib.h中,那么为什么在这里,没有包头文件stdlib.h,但包含了头文件iostream/ostream,为什么会出现重定义呢?
答:因为,头文件iostream/ostream中可能间接包含了头文件stdlib.h,根据上述结果可知,头文件iostream/ostream在VS编译器下也间接包了stdlib.h头文件,但是在其他编译器下,并不一样间接包了头文件stdlib.h,就比如,在VS编译器下,头文件iostream就间接包了头文件stdio.h,但是在linux系统下,头文件iostream就没有间接包头文件stdio.h,不同的编译器有不同的结果、
命名空间的使用及举例:
注意,此时头文件stdlib.h中包含的全局库函数rand与命名空间 lcc中的全局变量rand重名,要注意,虽然 int rand = 0 放在了命名空间 lcc 的{ }内部,但要清楚的是,该变量仍是一个全局变量,放在了命名空间 lcc 的内部,并没有变成局部变量,仍是全局变量,其生命周期仍是整个工程,只不过,它的作用域发生了改变,从原来的全局域变成了现在的命名空间域,但是两者不在同一个作用域中,所以是可以的,其次还要知道,若想使用命名空间 lcc中的内容,在使用的时候必须指定命名空间 lcc,若不指定命名空间 lcc 的话,如上图所示,标记符rand前面并没有:: ,则在寻找标记符rand时,若所找的标记符不是某个命名空间的名称时,则系统会跳过所有的命名空间去寻找标记符,若所找的标记符是某个命名空间的名称时,则就会跳过所以的不是命名空间的部分,在剩下的全是命名空间的部分中找以该标记符为名称的命名空间,而此处的rand前面没有:: ,并且rand也不是某个命名空间的名称,则系统会跳过所有的命名空间去寻找标记符,由于头文件stdlib.h默认在全局域内,故该头文件中包含了全局函数rand,所以printf函数中的rand使用的就是头文件stdlib.h所包含的全局函数rand,而rand是全局函数的函数名,函数名可以直接作为函数的地址,故相当于是函数指针,若以%d打印出来就是一个很大的数,至于该数是正还是负,根据地址去判断,若以%p打印,代表的就是全局函数rand的地址、
不可以在函数内部定义命名空间,只能在函数外部定义命名空间,故命名空间中内容的作用域均是命名空间域,注意,命名空间域和全局域是不同的域,但是这些内容(变量,函数等等)的性质都是全局的,即生命周期仍是全局的,就目前而言,全局域,局部域,命名空间域是两两不相同的域,命名空间域不影响生命周期、
如上图所示,若直接执行printf(\"%p\\n\", rand),rand前面不加域作用限定符::,并且标记符rand也不是某个命名空间的名称,则编译器从所有的命名空间之外去找标识符rand,直接可看成未定义命名空间即可,和原来的思路一样,但是若加上域作用限定符的话,则需要考虑具体来自于那个域,主要来自于命名空间域,全局域,没办法指定来自于局部域,要想得到局部域的话,考虑的是局部优先的原则,就要进行分类讨论:
当不存在命名空间时,也要考虑有没有加域限定符::的情况,因为除了命名空间域之外还有全局域和局部域之分,发现未加域限定符,按照上述总结的方法即可,根据局部优先的原则可知,此处打印的是局部变量a,即10,若想要在此代码的基础上打印全局变量a的话,应该怎么做呢?
可以通过加上域作用限定符来进行操作,如图所示:
在打印时,a的前面加上了域作用限定符::,并且域作用限定符::前面是空白,则默认从全局域中寻找标识符a, 即使用的就是全局变量a,打印出来就是1、
若想指定来自于命名空间域,可写成如图所示:
命名空间中的内容,既可以定义变量,也可以定义函数,还可以定义类型:
注意:同一个命名空间中内容,变量和变量不能重名,函数和函数不能重名,结构体名字和结构体名字(一般指结构体类型)不能重名,变量和函数不能重名,因为,同一个命名空间中的内容都在同一个命名空间域中,即在同一个作用域内,故不可以重名,否则会造成歧义,,但是,变量和结构体名字可以重名,函数和结构体名字型也可以重名,但是在使用的时候需要自己控制来调用,比如:
像上面的这种方式去写,编译器会把 Lcc::a 当作全局变量,而不是认为是结构体类型,如果想要把它当作结构体类型的话,就必须在其前面加上一个struct才可以,让编译器将其看成是一个结构体类型,如果a同时是函数名和结构体名字的时候同样也是如此,这个地方需要注意一下,在使用命名空间里的类型名的时候,struct如果要加上的话,只能加在命名空间名称的前面,如下面所示:
struct Lcc::a d;//正确的方式
Lcc::a struct d;//错误的方式
Lcc::struct a d;//错误的方式
此时还要注意一下,在VS2019下满足下述所说的,但是在VS2013下不满足,但本质上下述所讲的不会造成歧义,理论上是正确的,VS2019在该处对这部分做了优化、
结构体成员变量的名字和命名空间中的变量和函数重名也是可以的,因为这样是不会造成歧义的:
结构体成员变量的名字和结构体名字重名也是可以的,因为这样也是不会造成歧义的:
可以定义多个命名空间,不同命名空间中可以存在相同的变量名或者函数名和结构体名字:
上图中,在不同的命名空间中的变量a是两个不同的变量,但是两者的类型都是int整型、
如上图所示,命名空间Hjm中,变量名和结构体名字重复,是可以的,但是,若在main函数中,直接执行 Hjm::QueueNode 的话,系统会认为这是全局变量QueueNode,而不会把他当做结构体类型,如果在main函数中想得到结构体类型的话,只能在main函数中写成:struct Hjm::QueueNode d,但是,如果在某一个命名空间中,没有变量名或者函数名与结构体名字重复时,如下图所示:
此时,直接在main函数中使用: Lcc::QueueNode ,也可以得到结构体类型,这是因为在C++中,struct已经升级为类了,所以可以不加strcut,这是在某一个命名空间中,没有变量名或者函数名与结构体名字重复的前提下为基础的,但若在C中使用结构体类型的话,必须要加上struct,因为在C中,struct并没有升级为类,但是,,通常不这样写,若想得到结构体类型,,在命名空间中,不管是否有变量名或者函数名与结构体名字重复,最好都要在命名空间名称前面加上struct来得到结构体类型,若想得到变量名或者函数名,则就不需要在命名空间名称前面加上struct,在C中不存在命名空间的概念,故只在C++中进行上述的操作即可、
不同的命名空间中可以定义相同的结构体名字,如下图所示:
此时,若想得到结构体类型的话,则可执行:struct Test1::stu 和 struct Test2::stu ,,虽然两个命名空间中的内容都是一样的,但是对于结构体类型:struct Test1::stu 和 struct Test2::stu ,仍要看做是不同的类型,原因是因为来自于不同的命名空间,而结构体类型和命名空间是有关系的,可以理解为,结构体类型包含了命名空间,也可以说是自定义类型和命名空间有关,结构体类型属于自定义类型,但是,内置类型比如int,char等语言提供的类型是和命名空间无关,一般也不会去命名空间中取内置类型,所以只考虑自定义类型即可,通过下图就可以明白:
所以不可以使用不同的结构体类型定义相同的结构体变量,会造成基类型不同的重定义,如上图所示,也不可以使用相同的结构体类型定义相同的结构体变量,会造成真正的重定义,如下图所示:
命名空间的定义不能在函数内部进行,例如下面的写法就是错误的:
int main()
{
namespace stu
{
int a = 0;
}
return 0;
}
在工程中,命名空间一般都是定义在头文件中的、
虽然命名空间有着独立的域,但是我们无法对命名空间名称进行取地址操作,这种操作是非法的,目前而言,可以对变量,函数,字符串常量进行取地址操作,没有常量字符串这个概念,比如:
此处的a即为字符指针变量,因为a中存放的值是可以进行改变的,即字符指针变量a指向的位置是可以进行改变的,故称a为字符指针变量,可以对其进行取地址操作,则有:
这就是所谓的对变量进行取地址操作,也可以对字符串常量进行取地址操作,在此之前先要明确一下,凡是像 \"abcdef\" 这种用双引号引起来的字符串(若干个字符)都是字符串常量,因为该字符串的长度没办法再发生改变了,故称为字符串常量,凡是像这种 \'a\',用单引号引起来的字符都是字符常量,若是char a[2] = {\'a\',\'b\'};这样由字符组成的字符串不叫做字符串常量,因为该字符串的长度可以根据字符数组元素个数的改变再进行变化,这相当于是局部数组,存储在栈区上,而字符串常量则是存储在代码段上的,其次就是可以直接对字符串常量进行取地址操作,如图所示:
全局变量和静态变量放在静态区上,而全局函数的定义放在代码段上,对于函数而言,由于只能嵌套调用,不能嵌套定义的原因,所以,所有的函数均是全局函数,包括main函数,不存在局部函数的概念,而全局函数的定义也需要占空间,但是其大小不好进行计算,没有办法直接进行计算,函数调用是在栈区上,而函数的定义则在代码段上,命名空间中的变量是全局变量,其存放的位置位于静态区内,和全局变量一样是在程序运行前就已经开辟好空间的、
头文件中不应该包含using声明,这是因为头文件的内容会拷贝到所有引用它的文件中去,如果头文件里有某个using声明,那么每个使用了该头文件的文件都会有这个声明,则有可能产生始料未及的名字冲突,所以using声明一般使用在.cpp文件中、
命名空间的嵌套定义是为了防止同一个命名空间中的变量、函数名或者结构体名发生冲突,可以无限嵌套,嵌套命名空间的定义和使用:
注意:嵌套定义的命名空间的名称是可以重复的,因为这样并不会产生歧义,如图所示:
同名的命名空间是可以同时存在的,编译器编译时会进行合并,例如下面是等价的:
namespace stu
{
int a = 0;
}
namespace stu
{
int b = 0;
}
//上面的两个命名空间定义和下面的一样、
namespace stu
{
int a = 0;
int b = 0;
}
嵌套定义的命名空间内部的变量或者函数名和外部的变量或者函数名是可以相同的,这也是它出现要解决的问题所在,例如下面的操作是合法的:
namespace stu
{
int a = 0;
void fun()
{
return;
}
struct n
{
int c;
char d;
};
namespace stu
{
int a = 1; //使用:stu::stu::a
void fun() //使用:stu::stu::fun()
{
return;
}
struct n //使用:struct stu::stu::n 或 stu::stu::n
{
int m;
char n; //这里若是n的话,只能在VS2019下运行成功,2013对这一块未优化,运行会报错,但本质
//上是正确的,不会出现歧义、
};
}
}
命名空间引入的使用:
格式:
using namespace 命名空间名;
可以用什么引入什么,只引入某个命名空间的变量或者函数或者类型,例如:
若不引命名空间std的话,只有使用库里面定义的东西才会使用std::,比如,若不引入命名空间std的话,若使用cout,则需要加上std::,即写成: std::cout,,这是因为cout不是关键字,而是库里面定义的一个变量,他是对象,不是关键字而关键字不是库里面定义的东西,所以当使用关键字时,不需要加上std::、
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\", a); // 该语句编译出错,无法识别a
return 0;
}
三种使用方式:
1、加命名空间名称及作用域限定符(::):
int main()
{
printf(\"%d\\n\", N::a);
return 0;
}
2、使用using将命名空间中成员引入:
using N::b;
int main()
{
printf(\"%d\\n\", N::a);
printf(\"%d\\n\", b);
return 0;
}
注意:
我们一般会使用这种方式:
using std::cout;
这样引入一些常用的,因为直接引入一个命名空间会造成命名污染,容易出现重定义的现象、
3、使用using namespace 命名空间名称引入:
using namespce N;
int main()
{
printf(\"%d\\n\", N::a);
printf(\"%d\\n\", b);
Add(10, 20);
return 0;
}
由于C++兼容C语言,所以在C++中仍可以使用C语言中的输入和输出语法,只不过,C语言中的输入和输出时,需要指定具体的类型、
#define _CRT_SECURE_NO_WARNINGS 1
#include
using namespace std;
//目前而言,在写C++程序时,先把上述两行代码写上,但并不是指只要写C++就一定要引入命名空间std,以后可能不必须放开全部、
//其次就是在写C程序时,该包含什么头文件就包含什么头文件,不要省略,这样在一定程度上还能够提高效率、
#include
#include
int main()
{
//C语言的输入和输出、
//int a = 0;
//scanf(\"%d\", &a);
//printf(\"%d\\n\", a);
/*char arr[10] = { 0 };
scanf(\"%s\", arr);
int sz = strlen(arr);
printf(\"%d\\n\", sz);*/
C++的输入和输出、
//int a = 0;
//cin >> a; //>> 流提取运算符、
//cout << a; //<< 流插入运算符、
对于运算符>>而言,即是右移运算符,也是流提取运算符,对于运算符<<而言,即是左移运算符,也是流插入运算符、
同一个运算符可能会存在多种用途,再如: &,即是取地址运算符也是引用运算符、
自动识别类型以及一行输入/输出多个,并且不同类型之间可以交互输入和输出、
//int a;
//double d;
//cin >> a >> d; //不需要指定具体类型,并且一行可以输入多个、
//cout << a << \" \" << d << endl; //不需要指定具体类型,并且一行可以输出多个,可视endl为一个变量,代表的是换行,等价于\'\\n\'、
//cout << a << \" \" << d << \'\\n\';
//cout << \"hello world\" << endl;
C++中在cin输入时,仍默认空格和回车进行多个值的分隔,空格一直都是分隔作用,而回车在遇到最后一个值之前的作用都是分隔,
当输入最后一个值后再敲回车代表的是结束而不再是分隔作用、
如何控制浮点型数据小数点后的位数现阶段不进行讲解,后面再进行阐述,因为比较麻烦,可以自己进行尝试理解,但若必须用到的话,不要忘记由于
C++是兼容C的,故若想控制浮点型数据小数点后的位数可以使用C语言中的语法,相比C++而言会简单不少、
在C++中,C和C++的输入和输出语法都可以使用,并且可以混合使用、
而在C语言中,由于是C++兼容C,而不是C兼容C++,故在C语言中,只能使用C语言的输入输出语言,不可以使用C++的输入输出语法、
//printf(\"%d %.3lf\\n\", a, d);
return 0;
}
//scanf和printf比cin和cout要快一些,主要是因为C++为了兼容C语言,要进行缓冲区的同步,所谓同步即指在C++中可以混着用C和C++的语法,会付出一定的代价、
//在C++的IO流中会有涉及,一般情况下不考虑这个问题,只有在部分特殊情况下会考虑,比如:大量进行输入或输出的OJ题中会考虑,即,使用scanf和printf会快一些,才能过这道题、
//在一些比较老的编译器,C++比较旧的标准下,可能会存在头文件,比如:VC6.0中,该头文件是系统自动带的,并且存在.h的后缀,该后缀不可删除和修改,所以该头文件不具有命名空间,
//则可以直接使用cin,cout,endl等等,在新的编译器下,就出现了这种带有std命名空间的头文件,之前的头文件就不再常用了、
说明:
2、使用C++输入输出更方便,不需增加数据格式控制,比如:整形-->%d,字符-->%c、
#include
using namespace std;
int main()
{
int a;
double b;
char c;
cin >> a;//一次输入一个数据
cin >> b >> c;//一次可以输入多个数据,默认以空格或者换行进行分割
cout << a << endl;//输出变量a中存储的值和endl(换行符)
cout << b << \" \" << c << endl;//输出变量b的值和空格和变量c的值还有换行符,说明一次可以输出多个数据
return 0;
}
和C语言不同的是:
无论是输入还是输出,我们都不需要指定相应的类型,编译器会自动进行类型识别和转换、
3、在C++中,>>是流提取运算符,<<是流插入运算符、
cin >> a;//从cin(键盘)输入数据,然后数据被提取到了变量a中,这就是C++中变量的输入、
cout << a << endl;//将a插入到标准输出控制台(一般是显示器)中去、
C++中函数的参数(形参)也可以配备胎 、
void TestFunc(int a = 0) //缺省参数或默认参数、
{
cout<
注意:C++中不支持下面的语法:
在使用时,至少传一个实参、
注意:
1、半缺省参数必须从右往左依次来给出,不能间隔着给、
不管是全缺省参数还是半缺省参数,在main函数中的调用函数传实参时的规则都是一样的,具体见上面全缺省参数中的总结、
//a.h
void TestFunc(int a = 10);
// a.c
void TestFunc(int a = 20)
{}
// 注意:如果声明与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。
注意:此时又会出现两种情况:
1、声明给默认参数,定义不给默认参数,如下所示:
void fun(int a = 20);
void fun(int a)
{
cout << a << endl;
}
int main()
{
fun();//输出结果为20
fun(10);//输出结果为10
return 0;
}
2、声明不给默认参数,定义给默认参数,如下所示:
void fun(int a);
void fun(int a = 20)
{
cout << a << endl;
}
int main()
{
fun();//程序无法正常运行,函数不接受0个参数、
//原因:在链接之前,各个cpp文件会生成.obj文件,假设在声明中不给默认参数, .h头文件在源文件中展开,程序在编译时程序无法找到它的默认参数,程序只有在链接的时候才会找对应函数的地址(定义部分),才能知道它的默认参数,即编译阶段只能拿到声明,无法拿到定义,自然无法知道定义中的默认参数、
fun(10);//输出结果为10
return 0;
}
//缺省参数是在编译阶段进行处理的、
#define _CRT_SECURE_NO_WARNINGS 1 #include
using namespace std; #include //#include 在C++中,两者使用都是可以的、 //前者没有命名空间,后者存在命名空间std,在C++中写C程序也害怕C程序会存在命名冲突,故就把头文件改成后者带命名空间的类型, //这样就比较安全,要是写C++程序的话,尽量选择后者,但是两者都是可以在C++中使用的,只不过是尽量选择后者、 struct Stack { int*a; int size; int capacity; }; //初始化栈、 //此种情况适合使用半缺省参数,在某些情况下也有可能适用全缺省参数,此处以半缺省参数为例、 void StackInit(struct Stack* ps, int n = 4) { assert(ps); int* tmp = (int*)malloc(sizeof(int)*n); assert(tmp); ps->size = 0; ps->capacity = n; } int main() { //定义一个栈、 struct Stack st; //初始化栈、 //StackInit(&st);//此处进行传实参,第二个参数不给值,则在形参部分就默认传过来的实参为4、 //假设已知至少要在栈中存储100个数据,则可以写成如下所示: StackInit(&st,100);//这样就可以避免在前期进行频繁的扩容、 }