一、在C语言中,函数参数的传递方式有传值调用和传址调用
1、传值:在函数调用过程中会生成一份临时变量,最终把实参的值传递给新分配的临时变量即形参
优点:避免了函数调用的副作用,修改形参不会影响实参
缺点:无法改变形参的值,传值调用效率低,因为创建了一份临时拷贝,修改形参不会影响实参
2、如果想通过形参改变实参的值,只能通过指针传递:也生成一份临时拷贝
优点:一旦形参进行改变,可以反应到外部实参上去
缺点:在有的时候对形参的修改不需要反应到外部实参上去的时候,还是会反应到外部实参上去。若要实现对形参的修改不需要反应到外部实参上,则加上const的限制。指针使用前需要判断是否为空;在写程序时可能漏写*
指针可以解决问题,但不是很形象友好,不安全
二、引用:通过值传递,也可以达到修改外部实参的效果。引用就是别名
1、引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间类型& 引用变量名(对象名) = 引用实体;类型必须和引用实体是同种类型的
2、引用特性(&只有直接跟在类型的后面才是引用,&直接放在变量的前面叫做取地址)
(1)引用在定义时必须初始化
(2)一个变量可以有多个引用
(3)引用一旦引用一个实体,再不能引用其他实体
3、const类型的引用变量
若是引用变量为const定义的,则不能直接引用,需用const定义
int main()
{
const int a = 10;
const int &ra = a;
return 0;
}
引用变量不能引用普通常量,若需要引用,用const修饰
int main()
{
const int &b= 10;//b为10的别名
return 0;
}
引用类型的变量必须和实体一模一样:
以下会出现错误:
int main()
{
double d = 1.21;
int &rd = d;
return 0;
}
用const修饰即可:
int main()
{
double d = 1.21;
const int &rd = d;//因为引用变量和实体是相同的
//此处的rd与d不是一块空间,所以rd此时不是直接引用的d,二是先将d强转为整形,把浮点数的整数部分取过来,创建一个临时变量,rd引用的是临时空间,而不是d
//临时变量具有常性rd引用的是常量,需要用const修饰
return 0;
}
数组可以引用吗?如果可以,怎么实现?
数组不可以直接引用,以下代码出现错误
int main()
{
int a[10];
int &ra[10] = a;
return 0;
}//数组的类型是int []
更改正确后:
int main()
{
int a[10];
int (&ra)[10] = a;
return 0;
}
4、使用场景(c++里面有传址、传地址、传引用;引用结合了传址和传地址的优点)
(1)、引用变量直接引用一个别名:
int main()
{
int a=10;
int &ra = a;
return 0;
}
(2)函数传参的位置
void Swap(int &left, int &right)
{
int tmp = left;
left = right;
right = tmp;
}
int main()
{
int a = 10;
int b = 30;
Swap(a, b);
return 0;
}
(3)作为函数的返回值
int &Test()
{
int a = 40;
return a;
}
int main()
{
int b = Test();
printf("%d\n", b);
printf("%d\n", b);
printf("%d\n", b);
printf("%d\n", b);
return 0;
}//打印四个40//b为主函数内的一段空间,printf函数调用时不会改变值
下面的函数会产生错误:
int &Test()
{
int a = 40;
return a;
}
int main()
{
int &b = Test();
printf("%d\n", b);
printf("%d\n", b);
printf("%d\n", b);
printf("%d\n", b);
return 0;
}//打印一个40,后面三个为相同的随机值
注意:不能返回栈空间上的引用
错误原因:b接受的返回值为Test函数栈上的空间,一旦返回,将空间还给系统 ,但是不会擦除该空间的内容。下面的函数再调用时,该空间依然存在,但是值已经被修改过了。此时b为Test函数里面的空间
改正后:
法1:
int &Test(int &a)
{
return a;
}
int main()
{
int b = 40;
int rb = Test(b);
printf("%d\n", b);
printf("%d\n", b);
printf("%d\n", b);
printf("%d\n", b);
return 0;
}//打印4个40//a的生命周期比Test函数的生命周期长,函数调完之后还存在。//b没有被修改
法2:引用的是全局变量
int g_val = 40;
int &Test(int &a)
{
return g_val;
}
int main()
{
int b = 40;
int rb = Test(b);
printf("%d\n", b);
printf("%d\n", b);
printf("%d\n", b);
printf("%d\n", b);
return 0;
}//打印4个40//g_val变量在函数调用结束后没有被销毁
按照引用的类型返回时要么返回引用变量的参数,要么返回全局变量
全局变量、局部变量和引用变量的区别:(1)生命周期不同:按照引用的类型返回变量或者实体的生命周期一定比函数的声明周期长。
5、传值、传地址、传引用效率比较
(1)传参传引用的效率高于传值的效率,因为传值需要创建和销毁临时变量,耗费的时间长。传引用和传地址的时间一样快。传地址和引用在底层的处理方式相同,,把引用当成指针的形式来处理的。
指针和引用的共同点:都是按照指针的方式来处理的
(1)同一个指针可以指向不同的地址,而同一个引用变量只能引用一个变量。
int main()
{
int a = 10;
int b = 20;
int &ra = a;
int *pa = &a;
int *pb = &b;
}
(2)引用变量相当于是常指针
int main()
{
int a = 10;
int b = 20;
int &ra = a;//ra不能指向别的变量//int *const p
const int &cra = a;//ra不能指向别的变量,且不能修改外部实参指向不能改变,内容也不能改变//int int *const p
}
(3)常指针定义变量的之前必须进行初始化
int main()
{
int a = 10;
int b = 20;
const int *p1;
int const *p2;
int * const p3=&a;//p3在定义前没有初始化,则不能通过p3=&b改变变量的值//int &ra = a;
const int * const p4=&a;//const int &cra = a;
}
指针和引用的不同:
(1)代码的表现形式不同
(2)引用变量必须初始化,而指针变量不一定需要初始化,但是最好进行初始化
引用变量只能引用同一个实体,指针变量可以指向任意多个实体
(3)两个sizeof的含义不同,指针变量在32位平台下占4个字节,引用变量ra为1,引用变量计算的是引用的实体的类型的大小。
int main()
{
char *pa;
printf("%d\n", sizeof(pa));
char a = 'a';
char &ra = a;
printf("%d\n", sizeof(ra));
return 0;
}
引用变量ra++值为11;指针变量pa++偏移了一个整形变量的大小,pa指向连续空间++才有意义
int main()
{
int a = 10;
int &ra = a;
ra++;
int *pa = &a;
pa++;
return 0;
}
(4)指针需要判空,引用不需要,所以引用更加简洁和安全。
总结:
引用在定义时必须初始化,指针没有要求
一旦一个引用被初始化为指向一个对象,就不能再指向其他对
象,而指针可以在任何时候指向任何一个同类型对象
没有NULL引用,但有NULL指针
在sizeof中含义不同:引用结果为引用类型的大小,但指针始
终是地址*空间所占字节个数
引用自加改变变量的内容,指针自加改变了指针指向,偏移指针类型的大小
有多级指针,但是没有多级引用;引用常量,加上const就可以了,但不是多级引用,是右值引用,(多用于函数传参中),如下:
const int &&rra = 10;
指针需要手动寻址,引用通过编译器实现寻址
引用比指针使用起来相对更安全
错误收集:
int main()
{
int x = 10;
int *y = NULL;
Swap(x, *y);//相当于Swap(&x,y)//编译不会出现错误,但是参数*y在传参的过程中会遇到问题
return 0;
}
三、命名空间:在C++中,变量、函数和类都是大量存在的,这些变量、函数和类的名称将都存在于全局命名空间中,会导致很多冲突, 使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。为了解决命名冲突(名字污染)问题。
1、定义:一个命名空间相当于一个作用域,:可以定义变量或者函数
(1)普通的命名空间
namespace N
{
int a = 10;//命名空间作用域
void Test()
{
}
}
int a = 20;//全局变量
int main()
{
int a = 30;//局部变量
printf("%d\n", a);//打印局部变量
printf("%d\n", ::a);//打印全局变量;::作用域解析符
printf("%d\n", N::a);//打印命名空间作用域中的变量
system("pause");
return 0;
}
(2)用using将N1的命名空间引出来,引出来后即可直接使用
namespace N1
{
int a;
void Test()
{
printf("Test");
}
}
using N1::a;//用using将N1的命名空间引出来,引出来后即可直接使用,让命名空间作用域内的变量暴露在全局范围内
int main()
{
printf("%d\n", a);//打印命名空间中的变量
}
用using将N1的命名空间引出来,引出来后即可直接使用,让命名空间作用域内的变量暴露在全局范围内,如果存在全局变量会发生重定义问题,如下:
namespace N1
{
int a=10;
void Test()
{
printf("Test");
}
}
using N1::a;//用using将N1的命名空间引出来,引出来后即可直接使用,让命名空间作用域内的变量暴露在全局范围内
int a = 10;
int main()
{
printf("%d\n", a);//打印命名空间中的变量
}
使用using namespace N1后,访问命名空间作用域的函数和变量不用引用作用域名
(3)如果同一个工程里给的命名空间的名字相同,则把这些命名空间和到同一个命名空间里。相当于只有一个
namespace N1
{
int a=10;
void Test()
{
printf("Test");
}
}
using N1::a;
using namespace N1;
namespace N1
{
int b = 20;
void Test2()
{
printf("Test2");
}
}
int main()
{
N1::Test();
N1::Test2();
printf("%d\n", a);//打印命名空间中的变量
}
(4)命名空间可以嵌套:
namespace N1
{
int a=10;
void Test()
{
printf("Test");
}
}
using N1::a;
using namespace N1;
namespace N1
{
int b = 20;
void Test2()
{
printf("Test2");
}
namespace N2
{
void Test3()
{
}
}
namespace N3
{
void Test4()
{
}
}
}
int main()
{
N1::Test();
N1::Test2();
N2::Test3();
//去掉using namespace N1;后:N1::N2::Test3();
printf("%d\n", a);//打印命名空间中的变量
}
(5)没有名称的命名空间:没有名称的命名空间,相当于类里面的成员函数和成员变量是全局变量
namespace
{
int a = 10;
void test()
{
printf("test");
}
}
int main()
{
printf("%d\n", a);
return 0;
}
另外一个源文件不能引用没有名称的命名空间内的函数,只局限于当前文件。因为没有名称。
没有名称的命名空间和static全局变量的区别:
(6)命名空间里面定义的全局变量在命名空间里面相当于全局变量,在命名空间里嵌套的命名空间里面也可以调用
namespace N1
{
int b = 20;
void Test2()
{
printf("Test2");
}
namespace N2
{
void Test3()
{
printf("%d\n", b);
}
}
}
全局变量的类型:
2、说明:(1)一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中
(2)没有名称的命名空间,它的成员只在声明该命名空间的文件中可以访问,访问时不需要加命名空间名称,对于其他文件该命名空间中内容不可见
3、使用:
(1)直接在变量的前面加上命名空间
(2)使用using 命名空间名称::变量或函数名,将命名空间引过来
(3)使用using namespace 命名空间名,将命名空间里面的成员函数和成员变量都导出来 ,当作全局变量处理