C++入门(下)

目录

1. 引用

1.1 引用概念

 1.2 引用特性

1.3 常引用

1.4 使用场景

 1.5 传值 传引用效率比较

1.6 引用和指针的区别

2. 内联函数

2.1 概念

2.2 特性

3. auto关键字

3.1 auto简介 

 3.2 auto的使用细则

3.3 auto不能推导的场景

 4.基于范围的for循环

5.指针空值nullptr


1. 引用

1.1 引用概念

引用不是新定义一个变量,而是给已存在的变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间.

void Test()
{
	int a = 10;
	int& ra = a;//定义引用类型

	printf("%p\n", &a);
	printf("%p\n", &ra);
}

 注意:引用类型必须和引用实体是同种类型

 

 1.2 引用特性

1.引用在定义时必须初始化

2.一个变量可以有多个引用

3.引用一旦引用一个实体,在不能引用其他实体

void Test()
{
	int a = 10;
	//int& ra;	//该条语句编译时会出错
	int& ra = a;
	int& rra = a;
	printf("%p %p %p", &a, &ra, &rra);
}

1.3 常引用

void TestConstRef()
{
	const int a = 10;
	//int& ra = a;	//该语句编译时会出错,a为常量
	//int& b = 10;	//该语句编译时会出错,b为常量
	const int& b = 10;
	double d = 12.34;
	//int& rd = d;	//该语句编译时会出错,发生隐式转换时会产生一个临时变量,该变量具有常性
	const int& ra = d;
}

1.4 使用场景

1做参数.

void Swap(int& left, int& right)
{
	int tmp = left;
	left = right;
	right = tmp;
}

2.做返回值

int& Count()
{
	static int n = 0;
	n++;
	return n;
}

注意:如果函数返回时,出了函数作用域,如果函数对象还在,则可以使用引用返回,如果已经还给系统了,则必须使用传值返回.

 1.5 传值 传引用效率比较

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

#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;
}

 

 值和引用的作为返回值类型的性能比较

#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;
}

通过上述代码的比较,发现传值和指针在作为传参以及返回值类型上效率相差很大 

1.6 引用和指针的区别

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

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.引用比指针使用起来相对更安全


2. 内联函数

2.1 概念

inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率

 如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用。 

 

2.2 特性

1.inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体代替函数调用

缺陷:可能会使目标文件变大

优势:少了调用开销,提高程序运行效率

 2.inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。

 3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。

 


3. auto关键字

3.1 auto简介 

在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但遗憾的是一直没有人去使用它.C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

int TestAuto()
{
	return 10;
}

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

	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;

    //auto e;无法通过编译,使用auto定义变量时必须对其进行初始化
	return 0;
}

注意

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

 3.2 auto的使用细则

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;
	return 0;
}

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

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

void TestAuto()
{
auto a = 1, b = 2;
auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}

3.3 auto不能推导的场景

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

//此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}

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

void TestAuto()
{
	int a[] = { 1,2,3 };
	auto b[] = { 4,5,6 };
}


 4.基于范围的for循环

范围for的语法

在C++98中如果要遍历一个数组,可以按照以下方式进行:

void TestFor()
{
    int array[] = { 1, 2, 3, 4, 5 };
    for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
    array[i] *= 2;
    for (int* p = array; p < array + sizeof(array)/ sizeof(array[0]); ++p)
    cout << *p << endl;
}

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

void TestFor()
{
	int arr[] = { 1,2,3,4,5 };
	for (auto& e : arr)
		e *= 2;
	for (auto e : arr)
		cout << e << " ";
	return;
}

与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。


5.指针空值nullptr

NULL是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:

 #ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

 可以看到,NULL可能被定义位字面常量0,或者定义为无类型指针(void*)的常量.不论采取何种定义使用空值的指针时,都不可避免的会遇到一些麻烦,比如: 

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;
}

 程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。

因此,C++引入nullptr关键字

注意:

1.在使用nullptr表示指针空值时,不需要包含头文件

2.在c++11中,sizeof(nullptr)与sizeof((void*)0)所占字节数相同

3.为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值