网站设计建设网站,小程序赚钱的方式,三河燕郊最新消息,东莞网站建设制作软件目录
1、引用 --
1.1 引用的概念
1.2 引用特性
1.3 常引用 -- 权限问题
1.4 引用的使用场景
1.4.1 做参数
1.4.2 做返回值
注意
1.5 传值、传引用的效率比较
1.6 引用和指针的区别
2、内联函数
2.1 概念
转存失败重新上传取消编辑转存失败重新上传取消编…目录
1、引用 --
1.1 引用的概念
1.2 引用特性
1.3 常引用 -- 权限问题
1.4 引用的使用场景
1.4.1 做参数
1.4.2 做返回值
注意
1.5 传值、传引用的效率比较
1.6 引用和指针的区别
2、内联函数
2.1 概念
转存失败重新上传取消编辑转存失败重新上传取消编辑2.2 特性
3、auto
3.1 auto简介
3.2 auto的使用细则
3.3 auto不能推导的场景
3.4 auto与for合用 1、引用 --
1.1 引用的概念 引用不是新定义一个变量而是给已存在变量取了一个别名编译器不会为引用变量开辟内存空间它和它引用的变量共用同一块内存空间。 比如李逵在家称为“铁牛”江湖上人称“黑旋风”。同一个人只不过是两个名字。 语法 类型 引用变量名对象名 引用实体 是引用的符号在C语言中也表示取地址还表示按位与本质是运算符重载运算符重载一个符号会根据不同的场景编译器会自己确定含义。
我们举例来看看
int main()
{int a 10;int b a;//定义引用类型int c b;cout a a 地址 a endl;cout b b 地址 b endl;cout c c 地址 c endl;return 0;
}
运行结果 我们根据运行结果可以知道a,b,c 指的是同一块内存空间。
注意引用类型必须和引用实体是同种类型的。
1.2 引用特性 引用有三个特性 1. 引用在定义时必须初始化2. 一个变量可以有多个引用3. 引用一旦引用一个实体再不能引用其他实体。 其实前两条我们理解记忆就好了
1、引用是起别名要有对象我们才能再去起别名不存在对象给谁起别名
2、一个小孩妈妈可以叫他宝贝爸爸可以叫他贝贝爷爷也可以叫他狗蛋是吧所以一个对象可以有多个别名引用。
我们对这三个用代码写一下看看 1.3 常引用 -- 权限问题
我们用代码来看
int main()
{//1.权限放大const int x 10;int a x;return 0;
}
我们来看看编译会不会出错 这是因为在引用中对原变量的引用权限不能放大。
在这段代码中x是const修饰的常变量只能读取不能修改。而a是int类型针对类型来说它是可以修改的。因此这就是权限放大这是错误的。
我们继续往下看
int main()
{//2.权限平移const int i 20;const int j i;//3.权限缩小int z 30;const int y z;return 0;
}
我们看结果 对于权限的平移权限的缩小都是没有问题的由此我们可以看出在引用中对于权限来说平移、缩小都是没有问题的唯独要注意的是权限不能放大。
特殊
我们再往下看 直接能看出来对于引用来说不能初始化为常量这也算是权限的放大。 改为const修饰就不会报错了。
最后看一个 引用的时候不同的类型直接引用是会出错的。
1.4 引用的使用场景
1.4.1 做参数
void Swap(int left, int right)
{int tmp left;left right;right tmp;
}
在C语言的时候我们交换两个数我们使用指针来交换而C我们就可以使用引用来交换。
我们来测试一下 1.4.2 做返回值
我们先来看一段代码
int func()
{int n 0;n;return n;
}
int main()
{cout func() endl;return 0;
}
运行结果 这是是一个传值返回我们来深究传值返回的过程 传值返回的时候会产生一个临时变量跟传参一样临时变量会先把n拷贝下来然后再拷贝给函数调用传值返回的类型其实是临时变量的类型那么为什么要产生一个临时变量呢直接返回n不香吗 这是因为在函数调用的时候功能函数会建立函数栈帧而功能函数的每一条语句执行完后函数栈帧会自动销毁这时功能函数的整个函数体包括函数体里的所有内容都随之销毁返回的变量生命周期也就结束了。但是编译器在这里产生一个临时变量要是小就用寄存器存储将返回值拷贝给临时变量再又临时变量拷贝给调用的函数这就不会出错了。
有了上面的理解我们再来看一段代码
int func()
{int n 0;n;return n;
}
int main()
{int ret func();cout ret endl;cout ret endl;return 0;
}
运行结果 此代码的返回值是int而传引用是给变量起别名而在这里返回的是别名调用完func函数栈帧销毁了但是空间还在类似于订酒店我退房了但是房间还在别人还可以使用给n起了别名之后再去打印还是操作的n的那块空间那块空间可能被清理的也有可能还没有清理如果没清理那块空间的值还是1如果被清理了可能就是其他值了。 注意
我们看上面的代码在第二次打印的时候n的值明显就不正确了出了函数作用域func函数被销毁了我们再去访问那块空间的时候就是非法访问了这就是引用的一种野指针。
因此这里要注意如果函数返回时出了函数作用域如果返回对象还在(还没还给系统)则可以使用引用返回如果已经还给系统了则必须使用传值返回。
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 time: end1 - begin1 endl;cout TestFunc2 time: end2 - begin2 endl;
}
int main()
{TestRefAndValue();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;
}
int main()
{TestReturnByRefOrValue();return 0;
}
运行结果 我们看到无论是传参还是返回传引用的效率明显要高于传值。 原因 以值作为参数或者返回值类型在传参和返回期间函数不会直接传递实参或者将变量本身直接返回而是传递实参或者返回变量的一份临时的拷贝因此用值作为参数或者返回值类型效率是非常低下的尤其是当参数或者返回值类型非常大时效率就更低。 1.6 引用和指针的区别
在语法概念上引用就是一个别名没有独立空间和其引用实体共用同一块空间。在底层实现上实际是有空间的因为引用是按照指针方式来实现的。
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. 访问实体方式不同指针需要显式解引用引用编译器自己处理 9. 引用比指针使用起来相对更安全 2、内联函数
2.1 概念
以inline修饰的函数叫做内联函数编译时C编译器会在调用内联函数的地方展开没有函数调用建立栈帧的开销内联函数提升程序运行的效率。
我们来看一下平常我们写的代码
int Add(int x, int y)
{return x y;
}
int main()
{int ret 0;ret Add(1, 2);return 0;
} 我们可以看到这里是在调用函数但是我们要是不断要用Add函数的时候不断的调用效率会比较低因此在C中我们引入了内联函数inline。
inline int Add(int x, int y)
{return x y;
}
int main()
{int ret 0;ret Add(1, 2);return 0;
} 我们可以看到加了inline变为内联函数后就不再是调用了直接用函数体替换了函数调用不用开栈帧可以提高效率。
看到这是不是想到C的内联函数像是C语言的宏。
C中的内联函数确实和C语言的宏用途是一样的对于短小且频繁调用的函数C语言用宏来代替函数C中用内联函数。
C是全面兼容C语言的我们直接用宏就可以了那为什么我们还要使用内联函数呢
1、宏在写的时候容易出错且没有类型的检查还不能调试。
2、内联函数会对参数的类型进行检查还可以调试书写上就是正常的写功能函数在返回值类型前加inline。
如果想要看底层是调用还是直接展开的查看方式 1. 在release模式下查看编译器生成的汇编代码中是否存在call Add 2. 在debug模式下需要对编译器进行设置否则不会展开(因为debug模式下编译器默认不会对代码进行优化以下给出vs2019的设置方式) 2.2 特性 1. inline是一种以空间换时间的做法如果编译器将函数当成内联函数处理在编译阶段会用函数体替换函数调用缺陷可能会使目标文件变大优势少了调用开销提高程序运行效率。 2. inline对于编译器而言只是一个建议编译器会自动优化如果内联函数内存在循环/递归的时候编译器会自动优化忽略掉内联。一般建议10行以内 3. inline不建议声明和定义分离分离会导致链接错误。因为inline被展开就没有函数地址了链接就会找不到。 对于第二点我们做一下实验
inline int Add(int x, int y)
{int sum x y;sum x * y;sum x * y;sum x * y;sum x * y;sum x * y;sum x * y;sum x * y;sum x * y;sum x * y;return sum;
}
int main()
{int ret 0;ret Add(1, 2);return 0;
} 这里内联函数函数体一共写了十一行就算是函数调用了。
3、auto
3.1 auto简介 在早期C/C中auto的含义是使用auto修饰的变量是具有自动存储器的局部变量。C11中标准委员会赋予了auto全新的含义即auto不再是一个存储类型指示符而是作为一个新的类型指示符来指示编译器auto声明的变量必须由编译器在编译时期推导而得。 auto我们在C语言期间就接触过C语言期间定义的局部变量默认是用auto修饰因此我们在定义变量的时候从来不加auto也就没人在意。但是到了C11时期auto有了新的身份它可以自动推导类型。
我们来看一段代码看看auto的自动推导类型
int testAuto()
{return 1;
}
int main()
{int a 0;auto b a;auto c c;auto ret testAuto();cout typeid(b).name() endl;cout typeid(c).name() endl;cout typeid(ret).name() endl;return 0;
}
运行结果 这段代码里面 typeid(变量名).name() 是推导变量类型的一个函数。
我们可以看到auto很智能可以根据赋的值来推导类型。 注意使用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[] {456};
} 3.4 auto与for合用
按照C语言我们的写法遍历数组是下面的代码
int main()
{int array[] { 1, 2, 3, 4, 5 };for (int i 0; i sizeof(array) / sizeof(array[0]); i)array[i] * 2;for (int i 0; i sizeof(array) / sizeof(array[0]); i)cout array[i] ;cout endl;return 0;
}
运行结果 我们现在也可以使用auto这样来遍历数组
int main()
{int array[] { 1, 2, 3, 4, 5 };for (auto e : array)cout e ;return 0;
}
运行结果 我们这里使用的是范围 for for循环后的括号由冒号”“分为两个部分第一部分是范围内用于迭代的变量第二部分表示迭代的范围。这里会自动判断结束的。
这里的e是取到数组里的元素然后打印不会影响数组元素。
如果想改变数组元素我们可以使用auto e这是对数组元素起别名直接改变数组元素auto取到元素后会自动推导类型的。如下
int main()
{int array[] { 1, 2, 3, 4, 5 };for (auto e : array)e * 2;for (auto e : array)cout e ;return 0;
}
运行结果 我们可以看到结果这样写就把数组元素改了。