C++基础-命名空间-缺省参数-函数重载-引用-内联-auto-范围for

在这里插入图片描述

1. 命名空间

在C/C++中,变量、函数和类都是大量存在的,它们的名称都将存在于全局作用域中,可能会导致很多冲突。如下图:
在这里插入图片描述
因为time函数在全局作用域中,自己又在全局范围内声明了一个time对象,导致函数名与对象名重名,造成错误


为此产生了命名空间的概念:需要使用namespace关键字,后面接命名空间的名字,然后接一堆{}即可,{}中为命名空间的成员。

#include<iostream>
using namespace std;

//编译不通过
int time = 1;
int main()
{
    
    
	cout << "hello world!" << endl;
	cout << time << endl;
	return 0;
}	
//错误C2365“time” : 重定义;以前的定义是“函数”	


#include<iostream>
using namespace std;
//声明一个命名空间xty,使自己声明的变量在xty作用域,和全局作用域分开,因此
//使用时需要在前面加一个xty::表明去xty命名空间去找
namespace xty 
{
    
    
	int time = 1;
};
int main()
{
    
    
	cout << xty::time << endl;
	return 0;
}
// 可以运行

//输出为1


编译运行时,如果遇到未知的符号,没有特别的声明时,默认会先到全局作用域去寻找,因此在使用时,尽量声明它所在的作用域。

命名空间如何定义

#include<iostream>
using namespace std;


//声明一个命名空间xty1
namespace xty
{
    
    
	int time = 1;
	void PRINT()
	{
    
    
		cout << "xty" << endl;
	}
};

//命名空间可以嵌套定义
namespace ljj
{
    
    
	int time = 2;
	
	namespace ljj_PRINT {
    
    
		void PRINT()
		{
    
    
			cout << "ljj" << endl;
		}
	};
};

//同一个工程中允许存在多个相同的命名空间,比如a.cpp,b.cpp,a.h,b.h都又相同的命名空间xty
//编译器在链接时,会将它们自动合并成一个命名空间xty

int main()
{
    
    
	// 命名空间的使用

	//正常使用
	cout << xty::time << endl;
	xty::PRINT();

	//嵌套使用
	cout << ljj::time << endl;
	ljj::ljj_PRINT::PRINT();
	return 0;
}

在这里插入图片描述

命名空间如何使用

例子按上面代码来探究:

  • 加命名空间名称+作用域限定符::
int main ()
{
    
    
	//正常使用
	cout << xty::time << endl;
	xty::PRINT();
	return 0;
}
  • 使用using将命名空间的某个成员引入
// 仅仅将xty中的time成员引入,这样使用time就不用加域作用限定符了
using xty::time
int main ()
{
    
    
	//正常使用
	cout << time << endl;
	xty::PRINT();
	return 0;
}
  • 使用using namespace 命名空间名称 引入
// 将整个命名空间展开
//这样就可以不加域作用限定符使用xty下的所用成员
using namespace xty;
int main ()
{
    
    
	//正常使用
	cout << time << endl;	
	PRINT();
	
	return 0;
}

先在全局作用域中去寻找,如果没有再在展开的命名空间中去寻找
如下例子:

#include<iostream>
using namespace std;

//声明一个命名空间xty
namespace xty
{
    
    
	int time = 2;
	void PRINT()
	{
    
    
		cout << "xty" << endl;
	}
};
// 展开声明的命名空间
using namespace xty;

int main()
{
    
    
	cout << ::time << endl;

	return 0;
}

// 结果为time函数的地址

因为C++规定所有库函数都需要写在std的命名空间里面,using namespace std 仅仅是把std展开,只是一个命令,仍然需要把库函数头文件引进来!!保证有这个文件了,再将这个文件的std展开,就可以了。

2.缺省参数

缺省参数就是在声明或定义函数时,为函数的参数指定一个缺省值,当调用函数时,如果没有指定该参数,则默认使用缺省值。

#include<iostream>
using namespace std;

//设置缺省值为10
void Text(int a = 10)
{
    
    
	cout << a << endl;
}
int main()
{
    
    
	Text();
	//输出结果为10
	Text(20);
	//输出结果为20
	return 0;
}

2.1 缺省参数分类

2.1.1全缺省参数

所有参数都缺省

#include<iostream>
using namespace std;


//全缺省参数
void Text(int a = 10, int b = 20, int c = 30)
{
    
    
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
}
int main()
{
    
    
	Text();
	//输出结果为10,20,30

	//参数必须从左往右依次给出
	Text(1, 2);
	//输出结果 1,2 ,30
	Text(5);
	//输出结果5,20,30

	//必须从左往右给,传参错误!
	Text(, 0);
	return 0;
}

2.1.2 半缺省参数

只有部分参数缺省

#include<iostream>
using namespace std;


//半缺省参数
//缺省参数必须是从右往左连续的!
void Text(int a, int b = 2, int c = 3)
{
    
    
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
}
int main()
{
    
    
	Text(10);
	//输出结果为10,2,3

	return 0;
}

注意:1.缺省参数不能在函数声明和定义同时出现;同时出现时,声明说了算,要将定义处缺省删去。2.缺省值必须是常量或者是全局变量

3. 函数重载

C++允许在同一个作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数或类型或类型顺序)不同,常用来处理实现功能类似但数据不同任务。

#include<iostream>
using namespace std;

// 函数重载
int Add(int x, int y)
{
    
    
	return x + y;
}

//1. 参数类型不同
double Add(double x, double y)
{
    
    
	return x + y;
}

//2.参数数量不同
int Add(int x, int y, int z)
{
    
    
	return x + y + z;
}

// 3.参数类型顺序不同
int Add(int x, double y, int z)
{
    
    
	return x + y + z;
}
int Add(int x, int y, double z)
{
    
    
	return x + y + z;
}



int main()
{
    
    
	//感觉调用了一个函数一样,但是传进去了不同的数据类型
	//完成了不同的加法任务
	Add(1, 2);
	Add(1.11, 2.22);
	Add(1, 2, 3.33);
	return 0;
}

key:只有参数类型或参数顺序或参数个数不同才构成重载,返回值不同不构成重载!!!

4. 引用

引用就是给一个变量取别名。

使用符号&
类型& 引用变量名(对象名)= 引用实体
引用的类型必须和引用实体的类型相同

int main()
{
    
    
	
	int x = 10;
	//引用必须在声明的时候初始化,即声明的时候就得指出要给谁起别名。
	// int& y;
	int& y = x;

	cout << x << endl;
	cout << y << endl;
	//结果都是10

	return 0;
}

引用特性:1.引用一旦声明就必须初始化;2.一个变量可以有多个引用(一个变量可以有多个别名);3.引用一旦引用一个实体,就不能在引用其他实体

4.1 常引用

void Text()
{
    
    
	const int a = 1;
	//编译会出错,a本身为常量,因为区别名后权限放大
	//int& ra = a;
	const int& ra = a;

	//因为b为常量,编译的时候同样会出错
	//int& b = 10;
	const int& b = 10;  //正确写法


	double c = 1.11;
	//该语句编译时会出错,类型不同
	//int& rc = c;
	const int& rc = c;  //涉及到整型提升的问题,临时变量是右值,不可修改,因此const修饰能过

	int ii = 1;
	double dd = ii;//涉及到整型提成,dd实际存储的是临时变量,临时变量具有常性
	//double& rdd = ii;  //编译不过,因为临时变量具有常性,放大权限了
	const double& rdd = ii;//可以编过。
	
}

强制类型转换、整型提升,产生的都是临时变量,临时变量具有常性;如果在原地改变,有可能空间不够!!!

4.2 引用使用场景

4.2.1 做参数

//引用传值
void Swap(int& a, int& b)
{
    
    
	int tem = a;
	a = b;
	b = tem;
}

//传指针
void Swap(int* a, int* b)
{
    
    
	int tem = *a;
	*a = *b;
	*b = tem;
}
//对比发现,传引用比传指针更方便,代码量更少,更安全

4.2.2 做返回值

// 引用做返回值
//直接返回别名,可以直接改
int& Func()
{
    
    
	static int n = 0;

	//...
	n++;
	return n;
}


//传指返回
int Func()
{
    
    
	int n = 0;

	//...
	n++;
	return n;
}

函数返回时,会把当前函数的栈帧清空,返回值存储到一个临时的寄存器中,用来返回给调用者;如果不存到寄存器中,栈帧清空后,才返回到调用者函数,会导致原函数的数据先被OS清空,这样会丢失数据。因此,必须有一个能存储临时变量的寄存器。
使用引用作返回值的前提是:函数返回时,返回的变量不能还给操作系统,否则不能用引用返回,必须使用传值返回

4.2.3 传值和传引用效率分析

以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。

下面是一组代码用来测试传值和传引用的效率比较:

#include <time.h>
struct A {
    
     int a[10000]; };
void TestFunc1(A a) {
    
    }
void TestFunc2(A& a) {
    
    }
void TestRefAndValue()
{
    
    
	A a;
	// 以值作为函数参数
	size_t begin1 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc1(a);
	size_t end1 = clock();
	// 以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc2(a);
	size_t end2 = clock();
	// 分别计算两个函数运行结束后的时间
	cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
int main()
{
    
    

	TestRefAndValue();
	//cout << "hello world!" << endl;
	return 0;
}

下面一组代码用来测试返回值和返回引用的效率比较:

#include <time.h>
struct A{
    
     int a[10000]; };
A a;
// 值返回
A TestFunc1() {
    
     return a;}
// 引用返回
A& TestFunc2(){
    
     return a;}
void TestReturnByRefOrValue()
{
    
    
 // 以值作为函数的返回值类型
 size_t begin1 = clock();
 for (size_t i = 0; i < 100000; ++i)
 TestFunc1();
 size_t end1 = clock();
 // 以引用作为函数的返回值类型
 size_t begin2 = clock();
 for (size_t i = 0; i < 100000; ++i)
 TestFunc2();
 size_t end2 = clock();
 // 计算两个函数运算完成之后的时间
 cout << "TestFunc1 time:" << end1 - begin1 << endl;
 cout << "TestFunc2 time:" << end2 - begin2 << endl;
}

4.3 引用和指针的区别

在语法概念上引用就是一个别名,没有独立空间,和引用的实体共用同一块空间。

	int a = 10;

	int* pa = &a;
	*pa = 20;

	int& ra = a;
	ra = 20;

	cout << "&a = " << &a << endl;
	cout << "pa = " << pa << endl;
	cout << "&ra = " << &ra << endl;

    //输出的结果都是一个地址

然而他们的底层逻辑是一样的,我们看一下汇编代码:
在这里插入图片描述
可以发现,指针和引用的汇编代码完全一样,并没有什么不同,因此我们可以推断,引用就是使用指针实现的。

4.4 指针和引用总结

  • 引用在概念上定义一个变量的别名,指针存储一个变量的地址。
  • 引用在定义时初始化(必须指出引用了谁),指针没有要求。
  • 引用在引用一个实体之后,不能再次改变,指针可以随意指向任意一个实体。
  • 没有NULL引用,但可以有NULL指针。
  • sizeof中,引用的结果始终是引用类型的大小,但指针始终是地址的字节数
  • 引用自加即实体+1,指针自加地址偏移一个类型的大小。
  • 有多级指针,但是没有多级引用。指针更复杂
  • 访问实体方式不同:指针需要显式的解引用,引用需要编译器自己处理。
  • 引用比指针使用起来更相对安全。

5. 内联函数

内联函数是用inline关键字修饰的函数,编译时C++会在调用内联函数的地方展开,这样就没有函数调用建立栈帧的开销,可以提升程序的运行效率。用来替代C语言宏的语法

5.1 特性

  1. inline是一种以空间换时间的做法,如果编译器将函数当作内联函数处理,在编译阶段会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。(长函数与递归不适合内联)
  2. inline对于编译器只是一个建议,不同编译器关于inline实现的机制可能不同,一般建议:将函数规模小(函数不是很长)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline不作展开。
  3. inline不建议声明和定义分离,分离会导致链接错误。因为inline在声明处展开,没有函数地址 ,链接就会找不到。因此,声明和定义写一块。

5.2 面试题

  1. 宏的优缺点?答:优点:增强代码复用性;提高性能。缺点:不方便调试宏(宏在编译阶段替换掉了);导致代码可维护性差,可读性差,容易误用;无类型安全检查。
  2. C++有哪些技术替代宏?答:常量定义,换用const,enum;缩小函数定义换用内联函数。

6.auto关键字

使用auto关键字可以自动推到变量的类型,不需要再次写类型。

#include<iostream>
using namespace std;


int main()
{
    
    
	int a = 10;
	char b = 'x';
	auto c = a;
	auto d = b;

	//auto 自动推测处a, b的类型,并存储它
	cout << typeid(c).name() << endl;  // int 
	cout << typeid(d).name() << endl;  //  char
}

key:使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。

6.1 auto使用方法

  1. auto与引用和指针可以合用
	int x = 10;
	auto a = &x;  //类型是 int*
	auto* b = &x; //只是强调等号右边一定填指针,b的类型也是int*
	auto& c = x;  //给x取别名,c的类型引用

2.在同一行定义多个变量

	auto a = 1, b = 2;  //声明并定义,能编过
	auto c = 1, d = 2.2; //不能编过

当在同一行声明多个变量时,这些变量必须有相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

6.2 auto不能使用的场景

1.auto不能作为函数的参数

//此时会编译失败,auto不能用作形参类型
//因为编译器无法对a的实际类型进行推导
void test(auto a)
{
    
    
	;
}

2.auto不能直接用来声明数组

	// 声明数组
	int a[] = {
    
     1, 2, 3 };  
	auto b[] = {
    
     1, 2, 3 }; //编译不过

3.auto在实际中最常见的优势用法就是新式for循环,还有lambda表达式等进行配合使用。

7.基于范围for的循环表达式

对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号”:”分为两部分: 第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。

for(auto 迭代变量 :被迭代的范围 )

使用范例:

	int a[] = {
    
     1,2,3,4,5 };

	for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	{
    
    
		cout << a[i] << " ";  //  1 2 3 4 5
	}

	//使用auto推到的范围for
	//将a中的数据依次赋给data
	for (auto data : a)
	{
    
    
		cout << data << " "; // 1 2 3 4 5
	}


	//使用auto& 可以修改a中的数据
	//给a的数据取别名
	for(auto& data : a)
	{
    
    
		data++;    //此时a中的数据也被改变
	}

	//使用指针不能编译!!!
	for(auto data : &a)
	{
    
    
		(*data)++;   
	}

7.1 范围for使用方法

1.for循环迭代的范围必须是确定的!

void Test(int arr[])
{
    
    
	for (auto& data : arr)   //不能编译,因为传进来的arr是地址,编译器不能分辨出这个是数组
	{
    
    
		cout<<data<<endl;
	}
}

猜你喜欢

转载自blog.csdn.net/weixin_45153969/article/details/131265110