用什么软件快速做网站,视频直播间话术,谷歌网页版登录入口,营销型网站建设公司提供网站建设目录
一. 前言
二. 引用
2.1 引用的概念
2.2 引用的使用
2.3 引用的特性
2.4 常引用
2.5 引用的使用场景
2.6 传值、传引用效率比较
2.7 引用和指针的区别 三. 内联函数
3.1 内联函数的概念
3.2 内联函数的特性 一. 前言 上期说道#xff0c;C是在C的基础之上C是在C的基础之上容纳进去了面向对象编程思想并增加了许多有用的库以及编程范式等。我们介绍了部分C为了补充C语言语法上的不足而新增的内容如命名空间缺省参数函数重载等等上期传送门【C深入浅出】初识C上篇http://t.csdn.cn/UjbIo 本期将继续介绍C剩下的一些有趣的功能如引用内联函数等等这也是为了后面的类和对象打好基础。 话不多说直接上菜
二. 引用
2.1 引用的概念 引用并不是新定义一个变量而是给已存在变量取了一个别名编译器不会为引用变量开辟内存空间它和它引用的变量共用同一块内存空间。 引用就相当于我们给别人起昵称。例如你叫你女朋友小笨猪那么对你而言小笨猪就是你的女朋友和叫名字是一个意思既不是其他任何人你也不会因此多一个女朋友
2.2 引用的使用 类型 引用变量名(对象名) 引用实体
void Test()
{int a 10;int ra a; //定义引用类型此时ra就是变量a的别名ra与a是同一块内存空间printf(a的地址为%p\n, a);printf(ra的地址为%p\n, ra);
}int main()
{Test();return 0;
} 我们看到变量a和引用变量ra的地址是一样的说明它们共用同一块内存空间。 2.3 引用的特性 使用引用时需要注意以下几点特性 1、引用在定义时必须初始化
int main()
{int a 10;int b; //错误写法会报错int ba; //正确写法return 0;
} 2、一个变量可以有多个引用
int main()
{int a 10;//下面的bcd均是变量a的别名int b a;int c a;int d c;printf(%p %p %p %p\n, a,b,c,d);return 0;
} 3、引用一旦引用一个实体就不能引用其他实体
int main()
{int a 10;int b a; //b是a的别名int c 20; //能不能将b改成c的别名呢b c; //不行这条语句是将c的值赋给引用变量b即修改变量a的值并不是让b引用cprintf(a %p b %p c %p\n, a,b,c);printf(a %d b %d c %d\n, a, b, c);return 0;
} 4、引用类型必须和引用实体是同种类型的
int main()
{int a 10;double b a; //这种写法会报错return 0;
} 我们看到编译器报错说非常量限定那如果我们加上const修饰呢如下 const double b a; 我们惊讶地发现通过了编译说明上面不是因为int和double类型不一样而报错那究竟是为什么呢下面我们来分析分析 实际上由于引用实体和引用变量的类型不同编译器会自动进行隐式类型转换。编译器会生成一个double类型的临时变量tmp然后将a的内容以某种形式放到临时变量tmp中最后再让b引用临时变量tmp。
int main()
{int a 10;const double b a;//类似于下面的步骤int a 10;double tmp a; //将a的值转换赋给tmpconst double b tmp; //b再引用tmpreturn 0;
} 由于临时变量具有常属性因此tmp的类型就是const double用double类型的引用变量引用const double类型的变量这无疑是一种权限的放大是不被允许的。就好比别人大门紧缩不然你进你偏偏另辟蹊径从窗户翻入这无疑是犯法的私闯民宅。这就是为什么编译器会报出非常量限定的错误的原因引用变量d需要加上const修饰权限的平移是被允许的。 最后本来临时变量tmp在当前语句结束后就会被销毁但此时被b所引用其生命周期就自动被延长了。 分析了这么多下面我们用代码来进行验证一下
int main()
{int a 10;const double b a;printf(a %p , b %p\n, a, b); //求ab空间的地址printf(修改前 a %d , b %.2lf\n, a, b);a 20;//b 30; //这句代码会报错被const修饰的变量不可修改printf(修改后 a %d , b %.2lf\n, a, b);return 0;
} 我们发现a的地址和b的地址不同这说明了b并不是变量a的引用而是引用了新形成的临时变量。并且当我们对a进行修改时b中的内容并没有发生改变这也印证了a和b不是同一块内存空间。最后当我们想要对b的内容进行修改时编译器会直接报错说明b所在的空间具有常属性。 2.4 常引用 被const关键字修饰的引用变量我们称为常引用。我们无法通过常引用来修改引用实体的值如下
#includeiostream
using namespace std;
int main()
{int a 10;const int b a;//b; //会报错,b是常引用无法修改a; //a是普通变量允许修改cout a a b b;return 0;
} 前面我们提到了权限不能放大也就是说普通引用不能引用常属性变量。但是权限允许平移或者缩小即常引用可以引用常属性变量、常引用可以引用普通变量。如下
#includeiostream
using namespace std;
int main()
{int a 10;const int b a; //权限的缩小const引用引用普通变量编译正常const int aa 10;const int bb aa;//权限的缩小const引用引用const变量编译正常int cc aa;//权限的放大普通引用引用const变量报错return 0;
} 2.5 引用的使用场景 引用的使用场景一般有两个做函数参数、做函数返回值。 1、引用作为函数参数 在C语言中如果我们调用函数时使用传值调用那么形参的改变是不会影响实参的形参是实参的临时拷贝。如果我们想在函数中对实参进行修改那就必须使用传址调用通过地址对实参的值进行修改。 而在C中新增了引用的语法我们可以使用引用作为函数的形参此时形参就是实参的一个别名并不会额外开辟空间形参和实参共同内存空间修改形参也就是对实参进行修改。具体实现方式如下
#includeiostream
using namespace std;
void ModifyFun(int x) //引用作为函数参数
{x 100;
}
int main()
{int a 10;cout 调用前 a endl;ModifyFun(a);cout 调用后 a endl;
} #includeiostream
using namespace std;
void Swap(int x , int y) //引用作为函数参数
{int tmp x;x y;y tmp;
}
int main()
{int a 10;int b 20;cout 交换前: a a b b endl;Swap(a, b);cout 交换后: a a b b endl;
} 2、引用作为函数返回值 引用也可以作为函数的返回值如下
#includeiostream
using namespace std;
int Count()
{static int n 0; //n是一个静态变量函数调用结束后不会销毁cout n endl;;return n;
}
int main()
{int k Count();k;Count();return 0;
} 在Count()函数通过引用返回n此时main函数中的引用变量k就是n的别名当我们在main函数中修改k就相当于对静态变量n做修改。 但是如果以下情况使用引用返回会出现什么情况呢
int Add(int a, int b)
{int c a b; //c是局部变量Add调用结束后被销毁return c;
}
int main()
{int ret Add(1, 2);Add(3, 4);cout Add(1, 2) is : ret endl;return 0;
} 很惊讶的发现最终ret变量的值不是3而是7为什么呢 这就要来谈谈上述代码出现的野引用问题了。 我们通过下图来进行分析 总结函数返回时如果出了函数作用域返回对象还在(还没销毁还给系统)则可以使用 引用返回如果已经还给系统了则必须使用传值返回。 2.6 传值、传引用效率比较 在C/C中以值作为参数或者返回值类型在传参和返回期间函数并不会直接传递实参或者将变量本身直接返回而是传递实参或者返回变量的一份临时拷贝因此用值作为参数或者返回值类型需要额外进行拷贝效率是非常低下的尤其是当参数或者返回值类型非常大时效率就更低。 而如果以引用作为参数或者返回值类型由于引用是作为变量的别名并不会额外开辟空间形成拷贝。因此在传参和返回期间就相当于直接传递实参或将变量本身直接返回效率大大提升。下面我们通过代码来更直观地看看二者的效率差距 值和引用作为函数参数的效率差距
#include time.h
struct A { int a[10000]; };
void TestFunc1(A a)
{;
}
void TestFunc2(A a)
{;
}
void TestRefAndValue()
{A a;// 以值作为函数参数size_t begin1 clock(); //clock()函数返回程序运行到调用clock()函数所耗费的时间单位是msfor (size_t i 0; i 100000; i)TestFunc1(a);size_t end1 clock();// 以引用作为函数参数size_t begin2 clock();for (size_t i 0; i 100000; i)TestFunc2(a);size_t end2 clock();// 分别计算两个函数运行结束后的时间cout TestFunc1(A)-time: end1 - begin1 endl;cout TestFunc2(A)-time: end2 - begin2 endl;
}
int main()
{TestRefAndValue();return 0;
} 值和引用作为返回值类型的效率差距
#include time.h
struct A
{int a[10000];
}a;
// 值返回
A TestFunc1()
{ return a;
}
// 引用返回
A TestFunc2()
{ return a;
}
void TestRefAndValue()
{// 以值作为函数的返回值类型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()
{TestRefAndValue();return 0;
} 通过上述代码的比较我们发现传值和引用在作为传参以及返回值类型上效率相差很大。传引用的效率远高于传值。因此能使用引用就尽量使用引用提高效率。 2.7 引用和指针的区别 在语法概念上引用就是一个别名没有独立空间和其引用实体共用同一块空间。 但在底层实现上实际是有空间的因为引用是按照指针方式来实现的。这点我们可以参照二者编译后生成的汇编代码证明
int main()
{//引用int a 10;int ra a;//指针ra 20;int* pa a;*pa 20;return 0;
} 可见引用和指针的汇编代码是一模一样的最后都是通过变量a的地址来修改a。 不过引用和指针还是有不同点的如下 引用概念上定义一个变量的别名指针存储一个变量地址。引用在定义时必须初始化指针没有要求引用在初始化时引用一个实体后就不能再引用其他实体而指针可以在任何时候指向任何一个同类型实体引用必须初始化故没有NULL引用但有NULL指针sizeof的含义不同sizeof引用变量的结果为引用实体的类型大小而sizeof指针始终是地址空间所占字节个数(32位平台下占4个字节64位平台下占8个字节)引用自增即引用的实体增加1指针自增即指针向后偏移一个类型的大小有多级指针但是没有多级引用访问实体方式不同指针需要显式解引用引用编译器自己处理从安全性的角度引用比指针使用起来相对更安全 三. 内联函数
3.1 内联函数的概念 以inline关键字修饰的函数叫做内联函数编译时C编译器会在调用内联函数的地方展开不会调用函数建立栈帧因此内联函数提升程序运行的效率。 我们可以通过汇编代码来验证加上inline的函数是否会被调用 没加inline关键字:
int Add(int x, int y)
{return x y;
}
int main()
{int ans 0;ans Add(1, 2);return 0;
} 加上inline关键字:
inline int Add(int x, int y)
{return x y;
}
int main()
{int ans 0;ans Add(1, 2);return 0;
} 可以看到内联函数并不会生成对应的call指令而是直接被替换到函数调用处减少了调用函数建立栈帧的开销。 注意事项 内联函数的效果需要在release模式才会体现。因为在debug模式下编译器默认不会对代码进行优化顾不会进行展开。当然我们也可以进行设置方法如下VS2022
1、找到当前项目属性设置页 2、设置调试信息格式 3、设置内联函数扩展 3.2 内联函数的特性 主要有如下几点特性 inline是一种以空间换时间的做法如果编译器将函数当成内联函数处理在编译阶段会用函数体替换函数调用。缺陷可能会使目标文件变大优势少了调用建立栈帧开销提高程序运行效率。inline对于编译器而言只是一个建议不同编译器关于inline实现机制可能不同一般建 议将函数规模较小(即函数不是很长具体没有准确的说法取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰否则编译器会忽略inline特性(编译器也是很聪明的可不要贪杯噢)。以下为《Cprime》第五版关于inline的描述inline不建议声明和定义分离分离会导致链接错误。因为inline被展开符号表中就没有函数地址了链接就会找不到。 // in.h
#include iostream
using namespace std;
inline void f(int i);// in.cpp
#include in.h
void fun(int i)
{cout i endl;
}// main.cpp
#include in.h
int main()
{fun(10);return 0;
} 报错原因由于in.h文件中只有函数的声明没有定义顾在编译阶段main.cpp中的fun() 函数无法进行展开只能在链接阶段进行链接。但是由于in.cpp的fun()函数被声明为内联函数fun()函数并不会进入符号表最后就会导致链接时找不到函数地址报错。 以上就是本期的全部内容啦
制作不易能否点个赞再走呢