目录
- 引用
- 内联函数
- (C++11)
- auto关键字
- 基于范围的for循环
- 指针空值---nullptr
引用
引用:指将变量以另一个名称来展现的。它并非是一个新变量而是一个别名,它们同指一块内存空间。就如古时那些有字的人,亦或者是周树人,你说鲁迅是不是周树人呢?
引用的形式:类型& 引用变量名(对象名) = 引用实体;
常引用: const 类型& 引用变量名(对象名) = 引用实体;
用常引用时要注意,他是常数的这个特性。
void TestRef()
{int a = 10;int& ra = a;//注意引用的类型要与引用的对象的类型一致!!!printf("%p\n", &a);printf("%p\n", &ra);
//常引用const int a = 10;//int& ra = a; // 该语句编译时会出错,a为常量const int& ra = a;// int& b = 10; // 该语句编译时会出错,b为常量const int& b = 10;double d = 12.34;//int& rd = d; // 该语句编译时会出错,类型不同const int& rd = d;
}
引用的特点:
1.引用一定要初始化。
2.一个变量可以有多个引用。
3.一个引用不能指向多个变量。
引用的意义:
1.做参数。
2.做返回值。
对于引用做返回值有它的注意点:
#include<iostream>int& Add(int a, int b)
{int c=a+b;return c;
}
int main()
{int& ret = Add(1, 2);Add(3, 4);cout << "Add(1, 2) is :"<< ret <<endl;return 0;
}
你们认为结果是什么?其实是一个不可预测的值。
Add 函数里的 c 是局部变量,函数执行结束后,c 占用的内存会被释放。返回 c 的引用(int&)后,main 里的 ret 会变成悬空引用(指向已释放的内存 未定义行为)。后续调用 Add(3,4) 可能覆盖这片内存, 把 ret 原本指向的无效内存数据改掉 ,导致 ret 输出异常。
禁止返回局部变量的引用,否则会引发未定义行为;使用悬空引用 就是未定义行为 一切皆有可能。
做参数和返回值时 值与引用是有效率上的区别的:
#include<iostream>
#include <time.h>
using namespace std;
struct A { int a[10000]; };
void TestFunc1(A a) {
}
void TestFunc2(A& a)
{}
// 值返回
A TestFunc3(A a) { return a; }
// 引用返回
A& TestFunc4(A& a) { return a; }
int main()
{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;A b;A& B =b;// 以值作为函数的返回值类型size_t begin3 = clock();for (size_t i = 0; i < 100000; ++i)TestFunc3(b);size_t end3 = clock();// 以引用作为函数的返回值类型size_t begin4 = clock();for (size_t i = 0; i < 100000; ++i)TestFunc4(B);size_t end4 = clock();// 计算两个函数运算完成之后的时间cout << "TestFunc3 time:" << end3 - begin3 << endl;cout << "TestFunc4 time:" << end4 - begin4 << endl;return 0;
}
通过上述代码的比较,发现传值和指针在作为传参以及返回值类型上效率相差很大。
传值:简单直接,但拷贝大型对象时效率低。
传引用:避免拷贝,效率更高,适合传递大对象或需修改实参的场景。
实际开发中,优先用传引用(或 const 引用) 处理复杂类型,减少不必要的性能损耗。
你们可能听说引用其实就是指针,这是有一定来由的。
在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。
引用和指针的不同点:
- 引用概念上定义一个变量的别名,指针存储一个变量地址。
- 引用在定义时必须初始化,指针没有要求。
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何
一个同类型实体。 - 没有NULL引用,但有NULL指针。
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32
位平台下占4个字节)。 - 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小。
- 有多级指针,但是没有多级引用。
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理。
- 引用比指针使用起来相对更安全。
内联函数
内联函数:以inline修饰的函数叫做内联函数(在函数的返回值类型前面加inline)编译时C++编译器会在调用内联函数的地方展开(用函数体替换函数的调用),没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。
查看普通函数与内联函数的区别方法:
- 在release模式下,查看编译器生成的汇编代码中是否存在call Add。
- 在debug模式下,需要对编译器进行设置,否则不会展开(因为debug模式下,编译器默认不
会对代码进行优化,以下给出vs2013的设置方式)
debug模式下设置:
开始:
改变后:
开始:
改变后:
汇编代码:
内联函数:
特性本质:以空间换时间,编译阶段用函数体替换调用,减少调用开销、提升效率,但可能使目标文件变大。
编译器处理:inline 是给编译器的建议,编译器可选择忽略,通常规模小、非递归、频繁调用的函数建议用,递归等情况编译器可能忽略该特性 。
适用与限制:用于优化规模小、流程直接、频繁调用函数,多数编译器不支持内联递归函数,75 行这类较长函数也难在调用点内联展开 。
inline 函数应该声明和定义不分开。因为inline被展开,就没有函数地址了,链接就会找不到。
inline函数其实还可以替代宏,那么我开始回忆宏的优缺点。
优点:
1.增强代码的复用性。
2.提高性能。
缺点:
1.不方便调试宏。(因为预编译阶段进行了替换)
2.导致代码可读性差,可维护性差,容易误用。
3.没有类型安全的检查 。
而我们可以在C++用这两种方法来代替宏。
- 常量定义 换用const enum
- 短小函数定义 换用内联函数
(C++11)
auto关键字
在我们做的程序变得越来越大,我们所用的类型名称也会变得越来越难写(复杂)和含义不明确导致容易出错。对此我们之前可以用typedef来缩短名称,然而typedef 让类型别名和 const 的组合语义变得不直观,容易坑人。
typedef char* pstring;
int main()
{const pstring p1; // 编译成功还是失败?const pstring* p2; // 编译成功还是失败?return 0;
}
在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但遗憾的
是一直没有人去使用它,因为局部变量默认就是自动存储类型,使用 auto 修饰属于多余操作, 其修饰效果和不修饰一样,因此无人使用。
C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一
个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种类型的声明,而是一个类型声明时的占位符,编译器在编译期会将auto替换为变量实际的类型。因此使用auto定义变量时必须对其进行初始化。
auto 使用的细则:
1.auto 与指针和引用的注意事项:auto对于指针可以用auto或auto*,auto对于引用必须要加&:auto&。
2.auto对于同一行有多个变量的定义,他会先通过识别第一个变量类型,再用第一个变量类型赋给接下来的变量。因此要多个变量的类型一致。
3.auto不能用的场景:做函数参数,不能用在数组。
4. 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法
5. auto在实际中最常见的优势用法就是跟C++11提供的新式for循环,还有lambda表达式等进行配合使用。
基于范围的for循环
这个改动是为了对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for(auto& e : array)//第一部分:auto& e,第二部分:arraye *= 2;
for(auto e : array)cout << e << " ";
return 0;
}
与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。
基于范围的for循环使用条件:
1.因为是基于范围的,所以对于遍历的对象都要知其范围。
2.迭代的对象要实现++和==的操作
指针空值—nullptr
在C++98中的指针空值:NULL,NULL实际是一个宏,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++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器
默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void
*)0。
对于nullptr有下面的注意:
- 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入
的。 - 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
- 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。