目录
命名空间
在C语言中,我们会对变量,函数进行命名,但是会遇到名字相同而造成访问冲突的问题,那么在C++中,便有了一个“命名空间”的概念
定义
定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}
中即为命名空间的成员。
namespace an
{
int rand=10;
int Add(int left,int right)
{
return left+right;
}
struct Node
{
struct Node* next;
int data;
}
}
使用
1.部分展开,需要用到命名空间名称以及作用域限定符"::"
int main()
{
printf("%d",an::rand);
printf("%d",an::Add(1,2));
struct an::Node node;
return 0;
}
2.使用using将命名空间中某个成员代入
using an::rand;
int main()
{
printf("%d",rand);
printf("%d",an::Add(1,2));
return 0;
}
3.直接将命名空间名称引入
using namespace an;
int main()
{
printf("%d",rand);
printf("%d",Add(1,2));
struct Node node;
return 0
}
嵌套
命名空间也可以嵌套,那么需要多次用到作用域限定符
namespace an1
{
int a;
int b;
int Add(int left,int right)
{
return left+right;
}
namespace N2
{
int c;
int d;
int Sub(int left,int right)
{
return left-right;
}
}
}
int main()
{
printf("%d",an1::a);
printf("%d",an1::N2::c);
return 0;
}
合并
概而言之,就是一个工程里有多个同名的命名空间,最后编译器会将其整合,形成同一个命名空间,但注意的是,函数和变量不能有相同的名字,会导致重定义
//Test1.h
namespace an
{
void Test1Init()
{
cout << "Test1Init" << endl;
}
namespace an1
{
int rand = 1;
}
}
//Test2.h
namespace an
{
typedef struct Stack
{
int* a;
int top;
int capacity;
}ST;
}
C++的输入和输出
输出
C语言中,我们是用printf()输出函数来输出数据,那么在C++中呢?
首先C语言的printf函数需要一个stdio.h函数库,相同的,C++需要一个std的命名空间,因为C++将标准库(iostream)的定义实现都放入在这个命名空间中
简单实现:
#include<iostream>
using namespace std;
int main()
{
cout<<"Hello world"<endl;
return 0;
}
其中endl代表空格。
1.使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件
以及按命名空间使用方法使用std。
2.cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含< iostream >头文件中。
3.<<是流插入运算符,>>是流提取运算符。
输入
通过cin和>>进行输入
#include<iostream>
using namespace std;
int main()
{
int b;
cin>>b;
cout<<b<<" ";
return 0;
}
若是多个数据输入,则按照先后顺序从左到右放置输入即可
int b,c;
cin>>b>>c;
值得一提的是,C++的输入输出会自动识别变量类型,不需要%d %f %lf这些繁琐的识别符
除此之外,std作为标准库的命名空间,为了方便,我们使用代码即可
using namespace std;
这样标准库里的函数都会显示出来,方便我们使用
缺省参数
概念
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实 参则采用该形参的缺省值,否则使用指定的实参。
void Func(int a=0)
{
cout<<a<<endl;
}
int main()
{
Func();//若不传入参数,那么就会使用事先定义好的值
Func(10);//传入参数就用传入参数的值
return 0;
}
分类
全缺省参数
意思即为所有参数都提前定好值
void Test(int a=10,int b=20,int c=30)
{
cout<<"a="<<a<<endl;
cout<<"b="<<b<<endl;
cout<<"c="<<c<<endl;
}
半缺省参数
意思即为部分参数没有定好值
void Func(int a, int b = 10, int c = 20)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
注意:
1. 半缺省参数必须从右往左依次来给出,不能间隔着给
2. 缺省参数不能在函数声明和定义中同时出现
函数重载
C语言中,不能存在两个相同的函数名,但是在C++中,可以同时有多个相同函数名的函数
C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。
1.参数类型不同
int Add(int left, int right)
{
cout << "int Add(int left, int right)" << endl;
return left + right;
}
double Add(double left, double right)
{
cout << "double Add(double left, double right)" << endl;
return left + right;
}
2.参数个数不同
void x()
{
cout<<"f()"<<endl;
}
void x(int a)
{
cout<<"f(int a)"<<endl;
}
3.参数类型顺序不同
void f(int a, char b)
{
cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
cout << "f(char b, int a)" << endl;
}
注意!!!!返回值不同则不能形成函数重载
//不能形成函数重载
void f(int a, char b)
{
cout << "f(int a,char b)" << endl;
}
int f(char b, int a)
{
cout << "f(char b, int a)" << endl;
}
引用
概念
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空 间,它和它引用的变量共用同一块内存空间。
int main()
{
int a = 0;
int& b = a;//a和b本质上一样,地址也一样
cout << &a << endl;
cout << &b << endl;
b++;
cout << b << endl;//1
cout << a << endl;//相加的内容也是一样的
a++;
cout << a << endl;//2
return 0;
}
ps:引用类型必须和引用实体是同种类型的
特性
1. 引用在定义时必须初始化
2. 一个变量可以有多个引用
3. 引用一旦引用一个实体,再不能引用其他实体
void Test()
{
int a=10;
int&b;//错误
int& c=a;//a变量可以多个引用
int& d=a;
int b=20;
int&e =a;
int&e =b;//错误,已经引用了,不能再引用其他实体,也就是覆盖
}
常引用
1.作为参数
void Swap(int& left,int& right)
{
int tmp=left;
left=right;
right=tmp;
}
2.作为返回值
int& Count()
{
static int n = 0;
n++;
// ...
return n;
}
ps!!!
对于这个代码
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int& ret = Add(1, 2);
Add(3, 4);
cout << "Add(1, 2) is :"<< ret <<endl;
return 0;
}
返回的是随机值——为什么?
因为在运行函数Add的时候,会临时开辟一个Add函数的空间,此时用ret作为Add的别名,也就是ret和Add同一个空间,但是当Add结束后,空间会被收回释放,那么此时ret的值就会发生改变,可能不是原来需要的函数值,会变成随机值
由此可得:
若不清栈帧,那么函数Add运行的值便是ret的值
若清除栈帧,那么就会返回随机值,因为函数的栈帧空被销毁,ret和Add的空间是同一个,所以会返回随机值 ,这要根据编译器来决定
运用
对于顺序表,C语言代码是这样的
struct SeqList
{
int a[10];
int size;
};
int SLAT(struct SeqList* ps, int i)
{
assert(i < ps->size);
return ps->a[i];
}
//修改第i个位置的值
void SLModifystruct(SeqList* ps, int i, int x)
{
assert(i < ps->size);
ps->a[i] = x;
}
需要用两个函数来实现输入和读取
对于C++
int& SLAT(struct SeqList& ps, int i)//引用后ps就不是一个指针,而是可以视为实参结构体
{
assert(i < ps.size);
return ps.a[i];
//用结构体直接引用数组,因为结构体是定义在主函数里的,出SLAT函数后数组里的值还存在不会影响
}
int main()
{
//由于函数加上了引用,故可以根据别名直接修改第n个位置的值,别名直接修改!(侧面修改了对应的函数值)
struct SeqList s;
s.size = 3;
SLAT(s, 0) = 10;
SLAT(s, 1) = 20;
SLAT(s, 2) = 30;
cout << SLAT(s, 0) << endl;//获取第n个位置的值并且打印
cout << SLAT(s, 1) << endl;
cout << SLAT(s, 2) << endl;
return 0;
}
更多用法以后再讨论
指针和引用区别
在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
int main()
{
int a = 10;
int& ra = a;
cout<<"&a = "<<&a<<endl;
cout<<"&ra = "<<&ra<<endl;
return 0;
}
在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。
int main()
{
int a = 10;
int& ra = a;
ra = 20;
int* pa = &a;
*pa = 20;
return 0;
}
引用和指针的不同点:
1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
2. 引用在定义时必须初始化,指针没有要求
3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何 一个同类型实体
4. 没有NULL引用,但有NULL指针
5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32
位平台下占4个字节)
6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
7. 有多级指针,但是没有多级引用
8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
9. 引用比指针使用起来相对更安全
宏
C++中可以用宏来定义函数
#define Add(x,y) ((x)+(y))
但是其会有优缺点
优点:没有类型的严格限制,不需要建立栈帧,提高效率
缺点:容易错误,语法坑很多,不能调试,没有类型安全的检查
进而,引出了一个新的关键字——incline
内联函数
概念
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调 用建立栈帧的开销,内联函数提升程序运行的效率。
inline int add(int x, int y)
{
return x + y;
}
特性
1.inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会 用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运 行效率。
2.inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
// F.h
#include <iostream>
using namespace std;
inline void f(int i);
// F.cpp
#include "F.h"
void f(int i)
{
cout << i << endl;
}
// main.cpp
#include "F.h"
int main()
{
f(10);
return 0;
}
// 链接错误:main.obj : error LNK2019: 无法解析的外部符号 "void __cdecl
f(int)" (?f@@YAXH@Z),该符号在函数 _main 中被引用
auto关键字
1.根据右侧的变量,自动推导出类型
int main()
{
int a = 0;
auto b = a;//根据右边的类型,自动推导出左边的类型
auto c = &a;//c就是指针,int*
cout << typeid(c).name() << endl;
auto& d = a;//d就是引用,引用a
cout << typeid(d).name() << endl;
//简化代码——简化复杂类型的定义
//auto不能作为函数的参数,也不能直接用来声明函数
std::vector<std::string> v;
auto it = v.begin();
cout << typeid(it).name() << endl;
return 0;
}
但是auto不能作为函数的参数,也不可以直接用来声明函数
2.特殊的auto-for遍历
对于遍历一个数组,对于C语言中
int main()
{
int array[] = { 1,2,3,4,5 };
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
{
array[i] *= 2;
}
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
{
cout<<array[i]<<" ";
}
return 0;
}
但是在C++中,可以这样子实现:
for (auto e : array)
{
cout << e << " ";
}
cout << endl;
依次取数组的数据赋值给e,直到没有数据就会结束,会自动读取,自动迭代(++)
注意!!这个可以视为一个函数,从数组里读取数字然后打印,但若是想改变数组的数字,那么就需要用到别名来实现
for (auto x : array)
{
x *= 2;//此时无变化
}
for (auto& x : array)
{
x *= 2;//数组里的数字才会真正发生改变
}
但是在函数里,则不能使用——因为此用法只能用于有命名的,形参实际上不具有数组名
void TestFor(int array[])
{
for (auto& e : array)//此时不行,因为形参没有数组名的概念,只能重新定义
cout << e << endl;
}
不能用作函数的参数
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}
不能直接声明数组
void TestAuto()
{
int a[] = {1,2,3};
auto b[] = {4,5,6};//err
}
使用
1. auto与指针和引用结合起来使用
用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须 加&
int main()
{
int x = 10;
auto a = &x;
auto* b = &x;
auto& c = x;
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
*a = 20;
*b = 30;
c = 40;
return 0;
}
2.同一行定义多个变量
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译 器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
void TestAuto()
{
auto a = 1, b = 2; //pass
auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}
指针空值nullptr
在C语言中,NULL作为一个指针空值,但是在C++中NULL是个宏——定义为0或者被定义为无类型指针(void*)的常量。
所以若是想用NULL,则需要一个强制转换
void f(int)
{
cout<<"f(int)"<<endl;
}
void f(int*)
{
cout<<"f(int*)"<<endl;
}
int main()
{
f(0);
f(NULL);
f((int*)NULL);
return 0;
}
第二NULL个结果会按照0,作为整型去访问
因此为了避免麻烦,C++中将空指针定义为nullptr ——一个新的关键字