目录
一、namespace命名空间域
1.1namespace价值
为了解决C语言中命名冲突,C++中引用了namespace来解决在同一空间内中出现相同变量名。部分头文件中有相同的变量名冲突问题,namespace用于区分全局域和命名空间域之间的变量。程序在寻找变量时,会首先在局部中寻找,其次然后就算全局域(ps:命名空间域系统不会主动去找,需要自己声明,后面会讲解)。
1.2namespace的定义(使用)
namespace的定义实质上就是定义一个域,与全局域区分开,namespace定义和结构体定义一样(只不过结构体的花括号后面有分号,这里没有分号)。namespace定义的域名,遇到有全局域和namespace定义的域时,首先会使用全局域,当然这里要是全局域中没找到这个变量,及时namespace内有变量,也会报错,而当你想要使用namespace对应的域时,那就必须 (域名::变量) 这样使用才能访问到这个域的变量,这样就能使得不同的域使用相同的同名的变量(解决同名的冲突问题)。
无全局域、无局部域
#include<iostream>
namespace bear
{
int hight = 180;//cm
int weight = 75;//kg
}//没分号
int main()
{
std::cout << bear::hight << '\n' << bear::weight << std::endl;
return 0;
}
一个变量在全局域、局部域没命名时使用过程中需要使用一个::作用域解析操作符(一般不使用时是先寻找局部再全局)
有全局域、有局部域
#include<iostream>
namespace bear
{
int hight = 180;
int weight = 75;//kg
}
int hight = 160;
int weight = 60;
int main()
{
int hight = 170;
int weight = 70;
std::cout << bear::hight << '\n' << bear::weight << std::endl;
return 0;
}
和上面结果是一样的,180和75(如果没用作用域操作符那就会是170和70)。如果在这里我想要使用全局变量,那就直接使用::hight来使用(系统会将全局域为无名,从而想使用全局时,那就能::变量)。
std::cout << ::hight << '\n' << ::weight << std::endl;
C++中有局部域,全局域,命名空间域,类域,除了局部域和空间域有生命周期概念外,命名空间域和类域没有这一概念,但是消除这一变量也是全局,也就是生命周期和全局一样,但是就是没这一概念。namespace用于全局变量。namespace也能嵌套,在访问过程中也要使用 (域名::嵌套域名 ::变量)。
namespace Class
{
namespace lisi
{
int hight = 180;
int weight = 75;//kg
}
namespace zhangsan
{
int hight = 188;
int weight = 80;
}
}
int main()
{
std::cout << Class::lisi::hight << '\n' << Class::zhangsan::hight << std::endl;
return 0;
}
1.3命名空间使用
项目工程中多文件定义相同名域的namespace不会冲突。一般练习时,一直使用命名空间成员时,可以展开命名空间中所有成员,用using namespace 域名 来展开(放全局里面,项目不推荐容易冲突),当然using的使用也可以在命名空间内变量(用于有一个变量或函数使用频繁)格式为using namespace 域名(部分展开时using 域名::变量);一个是全部展开一个是部分展开,不用就是不展开。
展开全部
#include<iostream>
namespace bear
{
int hight = 188;
int weight = 80;
}
using namespace bear;
int main()
{
std::cout << hight << '\n' << weight << std::endl;
return 0;
}
部分展开
using bear::hight;
与上面不同的时这里没有使用namespac这个,这里bear::就已经表示使用了空间域。
二、C++输出和输入
C++用于输入输出的头文件include<iostream>,输出是std::cout,输入是std::cin,他们会自动识别类型,<<是流入运算符,>>流出运算符。可以一行输出输入(便利)如std::cout<<i<<d<<'\n';等于输出3个东西,等价于std::cout<<i;std::cout<<d;std::cout<<'\n';
using namespace std;
cout << "你好," << "欢迎来到" << "C++入门语法" << endl;
//等效于
printf("你好,欢迎来到C++入门语法\n");
#include<iostream>
using namespace std;
int main()
{
int age = 0;
cout << "多少岁了" << endl;
cin >> age;
cout <<"今年已经" << age <<"岁" << endl;//这里endl指的是结束标志
return 0;
}
三、缺省参数
缺省参数(缺省参数是声明或定义函数时为函数的参数指定一个缺省值),定义函数过程中可以不传参数,或者可以传参,这里函数里面定义要赋值,如果有传参就用传参如果没传参就会按函数中定义的默认。全缺省是指每个参数都有默认值,半缺省是指缺部分没有默认值。
using namespace std;
int Add(int a = 100, int b = 11)
{
return a + b;
}
int main()
{
int a = Add(10,20);//正常函数
int b = Add(10);//半缺省参数
int c = Add();//全缺省参数
cout << a << "\n" << b << "\n" << c << endl;
return 0;
}
在实参中:必须连续传参(从左到右依次传中间要么没参,后面也没参,如果有参会报错)
在形参中(函数中):也是必须连续填默认值(要么不填默认值,要么从右到左依次设置默认值,必须连续)。
缺省参数在创建多个项目时,给默认值是给到声明区域,也就是在头文件里面给默认值,不是多个项目时,那就可以放到定义中区。
四、函数重载
函数重载是C++同一作用域中出现相同函数名,这里解决的是出现相同函数名实习不同类型功能。要求同名函数的形参不同,参数个数不同或者形参类型不同(ps:函数类型时,只要形参有一个不相同就能重载,当然这里形参中可能会有函数指针,即使该参数为函数指针然后类型不同,但是函数指针内的形参列表相同那就不能重载)这里形参列表等价于形参类型指的就是函数参数个数,顺序和类型这三个。
#include<iostream>
using namespace std;
int Add(int a, double b)//原函数
{
return a + b;
}
//缺省参数对应的重构函数(日常应该避免缺省和重构一起的,除非是类型明显不一样时可以使用)
int Add(int a = 10, int b = 10)
{
return a + b;
}
//重构
double Add(double a, int b)//形参顺序不同
{
return a + b;
}
double Add(double a, double b)//形参类型不同
{
return a + b;
}
int Add(int a)//形参参数个数不同
{
return a + 0;
}
int main()
{
cout << Add(1, 2.0) << "\n" << Add(2, 5.5) <<"\n"<< Add(2.2,3.3)<< "\n" << Add(1) << endl;
return 0;
}
以上是符合重构条件
int Add(int a, double b)//原函数
{
return a + b;
}
double Add(int a, double b)//只是返回类型不一样
{
return a + b;
}
以上不符合重构条件
void Func(int a = 10)//原函数
{
//功能...
}
void Func()//重构函数
{
//功能...
}
void Func(int a = 10)//原函数
{
//功能...
}
void Func(double a =10)//重构函数
{
//功能...
}
void Func(int a = 10,int b =20)//原函数
{
//功能...
}
void Func(double a =10)//重构函数
{
//功能...
}
缺省参数的出现可以导致函数使用时不用传参,但是这里缺省参数的函数和不需要形参的函数导致了调用函数时,编译器不知道该如何调用,从而导致报错。在使用重构函数时应该尽可能避免缺省参数的包含,如果实在需要,那就必须最多一个函数为缺省参数函数,其他必须全是为正常函数,为了可维护,建议使用时还是形参类型很明显不同时使用缺省和重构并合的程序。
五、引用
5.1介绍
5.2引用的使用
类型& 引用别名 = 引用对象;如int a =10;int& b = a;其中b的地址就是a所对应地址,b是a的一个小名,改变其中一个值,进而改变他原本大名(小名)的值,如int c = 20;b = c;那就是c的值赋予到b和a中去,但是b地址不会改变(引用不会改变指向,所以这里是把c的赋值修改b中)。
#include<iostream>
int main()
{
int a = 10;
int& b = a;
int& c = b;
int& d = c;
std::cout << a << b << c << d << std::endl;//全是10
d = 20;
std::cout << a << b << c << d << std::endl;//全是20
return 0;
}
5.3功能
引用功能1:做函数形参,修改形参改变实参(归根就是把地址传过去了)对于二级指针也可以用引用如int*& a 来接受实参表示二级指针。
#include<iostream>
using namespace std;
void Spaw(int& change1, int& change2)
{
int tmp = change1;
change1 = change2;
change2 = tmp;
}
int main()
{
int a = 100;
int b = 200;
cout << "交换之前:" << a << " " << b << endl;//100 200
Spaw(a, b);
cout <<"交换之后:" << a << " " << b << endl;// 200 100
return 0;
}
// ⼀些主要⽤C代码实现版本数据结构教材中,使⽤C++引⽤替代指针传参,⽬的是简化程序
void SeqPushBack(SLT& sl, int x)
{
//功能实现...
}
typedef struct ListNode
{
int val;
struct ListNode* next;
}LTNode, * PNode;//这里分别表示typedef struct ListNode LTNode;和typedef struct ListNode* PNode;
// 指针变量也可以取别名,这⾥LTNode*& phead就是给指针变量取别名
// 这样就不需要⽤⼆级指针了,相对⽽⾔简化了程序
//void ListPushBack(LTNode** phead, int x)
//void ListPushBack(LTNode*& phead, int x)
void ListPushBack(PNode& phead, int x)
{
PNode newnode = (PNode)malloc(sizeof(LTNode));
newnode->val = x;
newnode->next = NULL;
if (phead == NULL)
{
phead = newnode;
}
else
{
//...
}
}
int main()
{
PNode plist = NULL;
ListPushBack(plist, 1);
return 0;
}
引用功能2:引用做返回值类型,修改返回对象(也能减少拷贝,减小拷贝内存,提高效率)。
typedef struct SeqList
{
int* arr;
int capacity;
int size;
}SLT;
//初始化
//用引用前
void SLTInit1(SLT* SL)
{
SL->arr = NULL;
SL->capacity = SL->size = 0;
}
//用引用后
void SLTInit2(SLT& SL)
{
SL.arr = NULL;
SL.capacity = SL.size = 0;
}
//用引用前
int* SLAt1(SLT* SL,int i)
{
assert(SL->size);
return &SL->arr[i];
}
//用引用后
int& SLAt2(SLT& SL, int i)
{
assert(SL.size);
return SL.arr[i];
}
在返回过程中其中函数内的变量会释放,但是会将返回的值拷贝下来放入编译器的临时变量(这个变量具有常性不可修改,以至于放回到主函数中不能通过函数调用直接修改),不过可以用引用取别名让他传地址进行修改,函数类型+&。
//这样写是不对的,函数执行完了函数栈帧会销毁,内部变量全还给操作系统
//返回值会放入一个临时变量中,而他全名都销毁了,会导致和野指针一样
//对于后面调用的Func2被b覆盖掉
#include<iostream>
int& Func1()
{
int a = 10;
return a;
}
int& Func2()
{
int b = 20000;
return b;
}
using namespace std;
int main()
{
int ret1 = Func1();
cout << ret1 << endl;
int ret2 = Func2();
cout << ret2 << endl;
cout << ret1 << endl;
return 0;
}
在引用过程中,int&类型函数返回别名函数时,是需要程序之前有这个值在里面的,而不能,而不能是在int&类型函数中定义的变量(会返回随机值或者原值)。
5.4特性
引用特性:1.引用必须初始化(int a =10;不能先int& r;再赋值r=a,而必须int& r = a;)
2.一个变量可以有多个引用(一个变量可以有多个别名)
3.引用一旦引用一个实体,不能再引用其他实体了(那个别名已经是别人的小名了,你不能把另一个人的别名/本名改为这个别名)
5.5const引用权限问题
指针和引用会涉及到权限问题,而权限只能缩小不能放大(也就是你有权利只能做你权利范围内的事,不能越界权限)举例来说const int x = 10; 就不能int& a = x;因为a是x的别名,那就可以通过a来扩权。同理const int* x = 20;也不能int * b=x;扩权,只能说是降权限制可以,或者同级也行。归根结底就算const会产生一个临时变量,导致限制权限。
const int a1 = 200;//常变量
//int& a2 = a1;//越权(防止使用别名来修改a1)
const int& a2 = a1;//同级
int b1 = 300;
int& b2 = b1;//同级
const int& b3 = b1;//降级(限制权力)
以上是引用权限
const int a1 = 200;//常变量
int* a2 = &a1;//越权
在指针中也会出现这样的问题和引用一样会越权,只要是同级或者降级即可。
double a = 2.0;
int& b = a;//越权
const int& b = a;//同级
解析: 在整形转换过程中都会出现这个临时变量,所以类似和const中设置的临时变量导致的。因为这个临时变量,使用引用时,那就会将a放入临时变量,然后再放入b中,这也就导致引用这个别名,在改变a时,它并不会改变b。这就是临时变量的用处,可以使得b引用的是临时变量的地址也就是临时变量的别名(深度理解临时变量对引用的作用原理)。
5.6与指针区别
引用和指针区别
1.引用不能改变指向,所以在链式结构中无法用引用替代他,还是得用指针(指针能不断改变指向对象)
2.引用必须初始化,而指针不必要初始化
3.删除空指针是无害的,而引用不能删除
4.指针很容易出现空指针和野指针的问题,引用会很少出现,一般会使用引用。
5.引用是取变量的别名,不用申请空间,而指针需要开辟空间。
6.引用在sizeof中也不同,引用大小指的是引用别人的大小(本质没空间开辟),而指针大小是32bit中4字节,64bit中8个字节。
六、内联函数
6.1介绍:传统宏函数优缺点引申内联函数概念
对于c语言中宏函数的定义的缺点是不能调试/复杂/类型安全检查。优点就是预处理阶段替代,不用建立函数栈帧,是一种提效。而针对这个缺点我们可以用内联函数inline修饰的函数叫做内联函数,编译时C++编译器会在调⽤的地⽅展开内联函数,这样调⽤内联函数就需要建⽴栈帧了,就可以提⾼效率来创建函数,在release中会直接替代,在debug中不会替代方便调试。
6.2内联函数的使用
内联函数一般inline加在函数前面即可。
inline int Add(int a, int b)
{
return a + b;
}
在多项目中使用内联函数,如果是放入.h的内联函数,那就必须在.h内定义内联函数,而不能在.cpp中定义。当然如果是.cpp中某个地方使用,那就能在.cpp中直接定义。(内联函数会产生内部链接也就是预处理就处理好了,而不是地址。像一般函数产生的是声明然后定义它在预处理阶段不会还原回去,所以这里就是外部链接,会产生符号如下图要去F.o中寻找符号)这里只需要将内联函数和定义宏一样就好了,都是在预处理阶段就还原了,不需要寻找地址
在.h中
inline Add(int a,int b)
{
return a+b;
}
在test.cpp直接使用
#include<iostream>
using namespace std;
int main()
{
int ret = Add(2,6);
cout<<ret<<endl;
return 0;
}
6.3内联函数缺点
一般用于函数代码较小减小代码指令数目,对于代码量多的函数还是用定义函数创建函数栈帧合适,毕竟在预处理阶段,内联函数就把代码插入到调用中去了,如果调用次数过多会导致预处理将代码还原到调用中去,以至于代码指令变多,导致程序冗余(比如调用1w次函数而函数里面代码是100行,前提不是循环调用,那么内联函数质量是1w✖️ 100,而对于定义函数是1w+100)内。联函数中代码太多时,编译器会自动将内联改为函数栈帧函数。
七、nullptr
nullptr是为了解决c++中NULL为0的情况。c语言中NULL是(void*)0而C++指的是0,所以为了避免这个问题,这里用到nullptr来表示指针类型,而不是整数类型(不像在C++中的NULL表示0这个整数类型)
#include<iostream>
using namespace std;
void F(int x)
{
cout << "int x" << endl;
}
void F(int* x)
{
cout << "int* x" << endl;
}
int main()
{
F(0);
F(NULL);//c++中表示0
F(nullptr);
return 0;
}