文章目录
命名空间
解决c语言命名冲突问题
1.我们自己定义的变量,函数可能和库里面重名冲突
2.但是进入公司项目组里面,做的项目通常比较大,多人协助,会导致代码中命名冲突 c语言无法解决这个问题,除非换名字 c++提出了新语法,叫命名空间,
3.命名空间里面可以包含各种东西,函数,结构体,变量
#include<stdio.h> #include<stdlib.h> int rand = 0; int main() { printf("%d", rand);//如果没加stdlib.h这个头文件的化, //是可以正常运行的,但是如果包了这个头文件时,rand就是里面的函数 //因为在预处理的时候,头文件展开,里面有rand这函数 printf("%d", rand);//以十进制方式打印,出来的是地址 return 0; }
namespace空间-----是一个域
#include<stdio.h> //定义了一个叫b的命名空间,命名空间是全局的 namespace b { //他们还是一个全局变量,放到静态域, int rand = 0;//加这个就是为了避免冲突 int a = 1; } int a = 0;//全局域 int main()//函数里面叫局部域 //编译器会先在局部去找,再去全局去找,否则就会报错 { printf("%d\n", rand); printf("%d\n", b::rand);//::叫做域作用限定符,意思是去取左边这个域里面的,很明显要去b这个域里卖弄去找 int a = 1; printf("%d\n", a);//a为局部域里面去找 printf("%d\n", ::a);//a到左边的空白域里面去找,就是去全局域里面去找 return 0; }
namespace也可以嵌套包含
对嵌套包含的要用两层::
//命名空间里面还可嵌套 namespace bt//命名空间里面不仅可以定义变量,还可以定义函数,类型 { int rand = 10; int add(int left, int right) { return left + right; } struct node { int val; struct node* next; }; namespace n1 { int d = 2; } } //使用命名空间里面的的东西 //不同文件里面定义的同样的命名空间,会被合并成一个,命名空间 int main() { bt::rand = 10;//使用其与其他进行隔离 struct bt:: node nt;//要加bt::才可以找得到里面的东西 bt::add(1, 2);//这样就可以找到了,只是影响了编译器找的规则 bt::n1::d = 2;//运用指定,这样子可以把嵌套的引用出来 //这种可以做到最好的命名隔离, return 0; }
c++的输入输出
#include<iostream> //输入输出流 //using namespace std; //c++库的实现定义在一个std的一个命名空间中, int main() { cout << "hello world" << endl; return 0; }
#include<iostream> using std::cout; using std::endl; using std::cin; int main() { std::cout << "hello world" << std::endl; return 0; }
#include<iostream> using namespace std; //cout可以自动识别类型 int main() { cout << "hello world" << endl;//endl是用来换行的 double d = 1.2; int k = 12; cout << d <<" "<<k<< endl;//自动识别类型不用%d,不可以控制小数点的位数,c++d输入输出可以混在一起写 cin >> k >> d;//把数据流向k和d return 0; }
缺省
全缺省和半缺省
//缺省参数 void func1(int a = 0)//如果func1里面传了参数,就和传的是一样的,如果func1里面没有传参数,相当于实参是0,形参是a //把缺省值去当作实参传给a { cout << a << endl; } void func2(int a = 1, int b = 2, int c = 3)//全缺省 { cout << a << " " << b << " " << c << endl; } //半缺省,缺省一部分参数,必须从右往左缺省,必须连续缺省 void func3(int a,int b=32,int c=89) { cout << a << " " << b << " " << c << endl; } int main() { func(1); func1(); func2();//啥都不传 func2(13, 42);//从左往右给 func3(13);//a必须要传,缺省的可以不传 return 0; } struct stack { int* a; int top; int capacity; }; void stackinit(struct stack*ps,int capcity=4) { ps->a = (int*)malloc(sizeof(int) * capcity); ps->top = 0; ps->capacity = capcity; } /*int main() { struct stack st; stackinit(&st);//不知道栈最多可以存多少数据,就用缺省值初始化 stackinit(&st, 100);//知道最多可以存多少数据,就传值,减少增容次数,提高效率 return 0; }*/ //缺省参数不能同时在声名与定义中出现,推荐大家写在声名,方便查找
函数重载
函数重载:是函数的一种特殊情况,同一个函数有不同功能, 这些同名函数的形参列表(参数个数或类型或顺序)不同,用来处理实现功能类似的不同问题
函数名相同的不同函数
//1.参数的类型不同 int add(int left, int right) { cout<<left << " " << right << endl; return left + right; } double add(double left, double right) { cout << left << " " << right << endl; return left + right; } //2参数个数不一样 void f() { cout << "f()" << endl;//没传参数就调用他 } void f(int a) { cout << "f(int a)" << endl;//传了参数就调用他 } //顺序不同 void fs(int a, char b) { cout << " fs(int a, char b)" << endl; } void fs(char b,int a) { cout << " fs(char b,int a)" << endl; } //返回值不同不能构成承载,返回值不同不能区分,参数不同可以区别的 //void a(int a) //int a(int a) //缺省值不同,不能构成重载 //1. //void cz(int a) //{ // cout << "as" << endl; //} //void cz(int a = 21) //{ // cout << "asd" << endl; //} //2.形参个数不同,构成重载,但是使用的时候要小心 void as() { cout << "ead" << endl; } void as(int a = 21) { cout << "as" << endl; } int main() { cout << add(3, 2) << endl; cout<<add(3.3, 8.3)<<endl; f(); f(1); fs(2, 's'); fs('a', 32); as();//存在歧义 return 0; }
引用
引用就是给别的变量取别名
int main() { int a = 10; int b = a;//把a的值赋给b //引用定义。 int& c = a;//引用,在变量名和类型的中间就是一个引用,是a的引用, //c是a的别名 //c和a的地址和值都是一样的 //引用在语法层,我们要理解为这里没有开一个新空间,就是对原来的取了一个新名称叫做b //如李逵,黑旋风都是一个人 int a = 20;//a和c都改成20, int c = 30;//c和a都改成了30, //如李逵吃饭了,黑旋风也吃了 //黑旋风吃饱了,李逵也吃饱了 int* p = &a;//单独就是取地址 return 0; }
int main() { int a = 0; int& b = a;//相同类型不会产生临时变量 double c = 1.1; int m = c;//会产生一个临时变量, //同样类型的截断,类型的提升都会产生一个临时变量 //函数的返回 //传值返回也是临时变量 const int& r = c;//r是c的临时变量的别名,把精度丢了 cout << r<<m << endl; return 0; }
引用的特性
1.引用必须初始化
2.一个变量可以有多个引用
3.引用只能用一个实体
int main() { //1.引用在定义的时候必须初始化,引用是取别名,要说清楚是给谁取别名 int a = 10; int& b = a; //2.一个变量可以有多个引用, int k = 3; int& kt = k; int& ktt = k;//相当于可以有很多个外号, int& m = kt;//这样也可以,kt,k,m,kkt都是一个空间,一个人可以有很多个名字 //3.引用一旦引用了一个实体,再不能引用其他实体了, int d = 10; int& dt = d; int c = 20; //1.这里有两层,是让dt变成c的别名呢? 否 //还是把c赋值给dt呢? 是 //经过调试是把c赋值给dt,dt和d的值都变成了20 dt = c; return 0; }
引用的应用
1.引用做函数参数
void swap(int* a, int* b) { int tmp = *a; *a = *b; *b = tmp; } //cpp的实现 //1.做参数 void swap(int& r1, int& r2)//r1是x的引用,x的别名,传引用,传地址和传引用功能相同 ,形参的改变可以改变实参 { int t = r1; r1 = r2; r2 = t; } //传值和传地址,传引用构成重载,但是调用时,传值和传引用存在歧义,不知道是传值还是传引用, int main() { int x = 0, y = 1; swap(&x, &y); swap(x, y);//传引用 return 0; }
void slistpushback(slistnode*& phead, slistdate x)//指针的引用,别名不混在一起,phead的改变就是改变plist,不用传二级指针,好认识 { slistnode* newnode = buynode(x); //那么我们这个时候要找尾 //找到尾节点的指针 if (phead == NULL) { phead = newnode; } else { slistnode* tail = phead;//我们要让tail走到尾部去,而非走到空 while (tail->next != NULL) { tail = tail->next; }//找到了尾节点,链接新节点 tail->next = newnode; } }
//指针也可以使用引用 int main() { int a = 10; int& b = a; int* p = &a; int*& p2 = p; //p2也是地址,p也是地址,p2是p的别名 return 0; }
int* single(int* num, int numsize, int& returnsize)//returnsize就是传回去的长度的别名 {}
2.引用做返回值
传值返回会有拷贝,就会有空间
传引用返回就不会有拷贝,原样返回
如果使用的变量在函数使用完之后没有销毁或者返回的空间较大,就用引用返回
int dd(int a, int b) { int c = a + b; return c;//返回的是c的临时拷贝,相当于int tmp=3被传回去了,只能调用一次,调用完了之后就会变成随机值 } int& d(int a, int b) { int c = a + b; return c;//f这里返回的就是c别名, //c传给int& tmp,就是c,所以不会有拷贝造成的空间浪费 } //但是如果函数返回的时候,返回的对象出了函数的作用域还没有销毁,就可以用传引用返回 int& cout() { static int n = 12; n++; //…… return n;//n是一个静态变量,出了函数域不会消失,所以用传引用返回 }
int main() { int ret = dd(1,2); //int& ret=dd(1,2),这样是不行的 int r = d(1, 2); //这样写会存在非法访问,因为add(1,2)的返回值是c的引用,所以add栈帧销毁了以后,回去访问c位置的空间 //如果add函数栈帧销毁了,清理空间那么取c值的时候,取到的就是随机值,给ret就是随机值,当然这个取决于编译器的的实现 cout << ret << endl; cout << r << endl; return 0; }
struct stack { int* a; int top; int capacity; }; void func1(struct stack st)//传值参数,会有拷贝,有空间开辟,原样拷贝 { } void func(struct stack* st)//传地址参数,指针开辟了4个字节 { } //引用做参数,随时都可以,可以1.提高效率,2.形参的修改可以影响实参,输出型参数 void func(struct stack& rst)//传引用参数,rst就是st的别名,没有开辟空间 { } int main() { struct stack st; func1(st); //指针和引用可以构成函数重载,因为指针和引用是两种不同类型的参数 func(&st); func(st); return 0; } //传引用返回的价值 //1.提高效率,2.修改返回变量 int& at(int i)//处理作用域,还在就很有用 { static int a[10];//第一次进入就创建了一个数组 return a[i];//返回这数组第i个位置的别名 //变成了可读可写的, }
常引用
用const进行修饰,在做参数的时候尽量都加一个const进行修饰
//尽量使用const引用进行传参 void f(const int& a) { cout << a << endl; } //const 引用通吃 // const type& 可以接收各种类型的对象 int main() { //权限被放大了,a从一个只读不写的,变成b一个可读可写的就是错的 //const int a = 10; //int& b = a; //权限不变,就可以 const int a = 10; const int& b = a;// //权限缩小 int c = 2; const int& d = c;//从可读可写的变成只读不写的是可以的 double d = 1.11; int i = d;//类型转化,把d的整形部分转过去,不加引用可以 //int& k = d;// //d的临时变量是具有常性,只读不写,而int&具有可读可写, //普通引用引的是左值,const引用引的是右值 //临时变量具有常性,不能被修改, //const 接收就是常量了 return 0; }
引用的优点:
引用和指针的不同点
1.引用在概念上定义一个变量的别名(没有开空间),指针存储一个变量的地址
2.引用在定义的时候必须初始化,指针最好初始化,但是不初始化也不会报错(会变成野指针)
3.引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个相同类型的实体(如我们在链表的时候指向不同的地方)
4.没有NULL的引用,但是有NULL 的指针
5.在sizeof里面的含义不同,引用为引用类型的大小,但是指针是地址空间所占字节大小(32位下是4个字节或64位里面的8个字节)
6.引用自加**++是引用的那个实体+1**,而指针是指针指向偏移一个类型的大小
7.访问实体方式不同,指针需要解引用,引用编译器里面会自己处理
8.有多级指针没有多级引用,引用比指针更加安全
extern “C”
c++程序,调用c的库,再c++程序中加extern "C"
告诉c++编译器,{}里面的函数是用c的编译器编译的,链接的时候,用c的函数名规则去找,就可以链接上了,只有c++才认识extern “c”
c程序,调用c++的库,再c++库中加extern “C”
c掉c++,c++的静态库就会用c的函数名修饰规则去处理以下函数,所以就不能用重载
//在c程序调用c++时候,在c++库里面加extern "C" #ifdef __cplusplus #define EXTERN_C extern "C" #else #define EXTERN_C #endif // __cplusplus
内联函数inline
我们在每次调用函数都会建立栈帧,有栈帧就会有消耗,需要建立栈帧,栈帧中要保存一些寄存器,结束后又要恢复,
可以看到这些都是有消耗的,对于频繁调用的小函数,能否优化一下呢,
c语言可以用宏,来操作避免栈帧的建立
#define ad(x,y) ((x)+(y));
c++的实现方法inline,内联函数
有了inline就不需要去用c的宏,因为宏很复杂,很容易出错 inline但是展开的话,指令就会特别多
1.inline是一种以空间换时间的做法,省去调用函数栈帧的过程,所以代码量长的函数,或者递归函数就不适合用inline,展开之后就会有特别多的指令,变得很大
所以小函数比较适合用inline
2.inline对于编译器只是一个建议,编译器会自动识别,递归或长的编译器会自动忽视inline
3.inline函数声名和定义不能分离,.h和.cpp都要加一个inline,
结论:短小或频繁使用的函数就定义成inline
inline int add(int x, int y)//这样就不会建立栈帧,内联函数,默认是不会展开的,没有函数调用了 { int ret = x + y; return ret; } inline void swap(int& a, int& b)//swap也会经常会出现 { int tmp = a; a = b; b = tmp; }