军用棉被门网站建设,网站建设需要哪些证,百度输入法,网站设计步骤包括6. 引用#xff08;引用就是取别名#xff09;
6.1 引用的概念和定义
引用不是新定义一个变量#xff0c;而是给已存在变量取了⼀个别名#xff0c;编译器不会为引用变量开辟内存空间#xff0c;它和它引用的变量共用同一块内存空间。比如#xff1a;水浒传中李逵…6. 引用引用就是取别名
6.1 引用的概念和定义
引用不是新定义一个变量而是给已存在变量取了⼀个别名编译器不会为引用变量开辟内存空间它和它引用的变量共用同一块内存空间。比如水浒传中李逵宋江叫铁牛江湖上人称黑旋风林冲外号豹子头
类型 引用别名 引用对象;
C中为了避免引入太多的运算符会复用C语言的⼀些符号比如前面的 和 这里引用也和取地址使用了同⼀个符号大家注意使用方法角度区分就可以。 int a 0; // 引⽤b和c是a的别名 int b a; int c a; // 也可以给别名b取别名d相当于还是a的别名 int d b; 这串代码在底层的角度是这样的 对指针变量取别名 int* p1 a; int* p2 p1; 指针变量的使用 typedef struct ListNode { int val; struct ListNode* next; }LTNode, *PNode; int main() { PNode plist NULL; ListPushBack(plist, 1); return 0; } 这里的*PNode是指把typedef struct ListNode* 定义为PNode。 6.2 引用的特性 • 引用在定义时必须初始化 • ⼀个变量可以有多个引用 • 引用一旦引用⼀个实体就不能引用其他实体 这里的d的地址没变说明d还是a的别名d没有指向e只是把e的值赋给了d。
当我们实现链表的时候我们要删除一个结点但这时的地址之间是相互关联的因为引用不能改变指向所以就不可能完成。所以C的指针引用不能完全替代指针。 6.3 引用的使用 • 引用在实践中主要是用于引用传参和引用做返回值中减少拷贝提高效率和改变引用对象时同时改变被引用对象。引用用作别名没有额外开辟空间可减少拷贝效率。 减少拷贝的案例 • 引用传参跟指针传参功能是类似的引用传参相对更方便一些。 • 引用返回值的场景相对比较复杂我们在这里简单讲了一下场景还有一些内容后续类和对象章节中会继续深入讲解。 • 引用和指针在实践中相辅相成功能有重叠性但是各有特点互相不可替代。C的引用跟其他语言的引用(如Java)是有很大的区别的除了用法最大的点C引用定义后不能改变指向 Java的引用可以改变指向。 • ⼀些主要用C代码实现版本数据结构教材中使用C引用替代指针传参目的是简化程序避开复杂的指针但是很多同学没学过引用导致一头雾水。 传值传参会生成一个拷贝传值返回也会生成一个拷贝。C中是这样规定的在红线这里进行一个传值返回返回这个对象的时候 他不会引用这个对象做函数调用的返回值不会返回要返回的东西它会生成一个临时对象把这的值给临时变量再用临时变量做这个整个表达式的返回值。STTopst11这是的加1就是加到临时对象上面。临时对象具有常性。
那临时对象是什么呢
临时变量通常是指编译器在栈里面临时开一块空间存储中间值的这种也有可能是用寄存器去存。
这里如何使用STTopst11呢只需要采用引用返回就可以了。传引用返回就是返回他的别名也就是这里的2。也就a数组指向的top-1这里的对象。这时候就把引用对象给改变了。 typedef int STDataType; typedef struct Stack { STDataType* a; int top; int capacity; }ST; void STInit(ST rs, int n 4) { rs.a (STDataType*)malloc(n * sizeof(STDataType)); rs.top 0; rs.capacity n; } // 栈顶 void STPush(ST rs, STDataType x) { // 满了 扩容 if (rs.top rs.capacity) { printf(扩容\n); int newcapacity rs.capacity 0 ? 4 : rs.capacity * 2; STDataType* tmp (STDataType*)realloc(rs.a, newcapacity * sizeof(STDataType)); if (tmp NULL) { perror(realloc fail); return; } rs.a tmp; rs.capacity newcapacity; } rs.a[rs.top] x; rs.top; } int STTop(ST rs) { assert(rs.top 0); return rs.a[rs.top - 1]; } int main() { ST st1; STInit(st1); STPush(st1, 1); STPush(st1, 2); cout STTop(st1) endl; //修改栈顶的数据呢 (STTop(st1)) 1; cout STTop(st1) endl; return 0; } 其实指针也可以做到 并不是任何场景都能用引用返回后面结合类和对象讲比如说 ret是局部变量这里类似于野引用。 int* fun( ) { int ret 10; return *ret; } 这里的指针就越界了指针越界不一定报错。 在vs中设置了抽查位置这两个位置不分配给别人给两个固定的值在程序运行结束时看这两个位置的值有没有发生改变没有被修改就说明没有越界。
6.4 const引用 • 可以引用一个 const 对象但是必须用 const 引用。const 引用也可以引用普通对象因为对象的访问权限在引用过程中可以缩小但是不能放大。const对象只能用const引用普通对象可以用const引用也可以普通引用。引用、指针存在权限的放大和缩小。 这里是把x的这块空间拷贝给给yx不能修改这块空间。
下面这个也是经典的权限放大 下图这个是权限的缩小 p1对于a的权限是只读不能写的p1拷贝给给p2p2又变成可读可写的了p2的权限被放大p5指向b的权限是可读可写的p6指向p5也是可读可写的这里不存在权限放大因为const修饰的是p5本身不是指向的内容。 • 需要注意的是类似 int rb a*3; double d 12.34; int rd d; 这样⼀些场景下 a*3的和结果保存在一个临时对象中 int rd d 也是类似在类型转换中会产生临时对象存储中间值也就是时rb和rd引用的都是临时对象而C规定临时对象具有常性所谓常性就像是被const修饰了一样所以这里就触发了权限放大必须要用常引用才可以。 • 所谓临时对象就是编译器需要一个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象 C中把这个未命名对象叫做临时对象。 a * 3这里存在一个临时变量临时变量具有常性这里加个const就行了。 这里的d给给rd其实也不是直接给过去的中间也会产生一个临时变量来存储中间的结果。d给给中间的临时对象这个临时对象是int类型临时对象再给给rd。 类型转换会产生临时对象d给了临时对象这里的临时对象给给rd。也就是说rd引用了临时对象。
那上面这些到底有什么用呢
这里的传值传参就不说了对于引用传参
void fun(int rx)如果变量 rx 不改变形参建议前面加 constvoid fun(const int rx),加const的好处是什么呢传参就非常宽泛。 const引用的价值是什么 1.可以引用const对象 2.可以引用普通对象 3.可以引用临时对象 6.5 指针和引用的关系 C中指针和引用就像两个性格迥异的亲兄弟指针是哥哥引用是弟弟在实践中他们相辅相成功能有重叠性但是各有自己的特点互相不可替代。下面的结论面试官可能会问 • 语法概念上引用是⼀个变量的取别名不开空间指针是存储⼀个变量地址要开空间。 • 引用在定义时必须初始化指针建议初始化但是语法上不是必须的。 • 引用在初始化时引用一个对象后就不能再引用其他对象而指针可以在不断地改变指向对象。 • 引用可以直接访问指向对象指针需要解引用才是访问指向对象。 • sizeof中含义不同引用结果为引用类型的大小但指针始终是地址空间所占字节个数(32位平台下占4个字节64位下是8byte) • 指针很容易出现空指针和野指针的问题引用很少出现引用使用起来相对更安全一些。 野引用的案例 从底层汇编的角度看引用也是用指针实现的。 7. inline • 用 inline修饰的函数叫做内联函数通常放到返回值的前面编译时C编译器会在调用的地方展开内联函数这样调用内联函数就不需要建立栈帧了就可以提高效率。 inline在这里的作用就是没有宏函数的坑也不用建立栈帧提效。 • inline对于编译器而言只是⼀个建议也就是说加了inline编译器也可以选择在调用的地方不展 开不同编译器关于inline什么情况展开各不相同因为C标准没有规定这个。inline适用于频繁 调用的短小函数对于递归函数代码相对多⼀些的函数加上inline也会被编译器忽略。 • C语言实现宏函数也会在预处理时替换展开但是宏函数实现很复杂很容易出错的且不方便调 试C设计了inline目的就是替代C的宏函数。 • vs编译器 debug版本下面默认是不展开inline的release版本下默认是展开inline的这样方便调试debug版本想展开需要设置一下以下两个地方。 可执行程序是一个文件它会以一个进程的角度来进行运行可执行程序会生成一个进程进程才会给它分配内存进程会把可执行程序的那个指令加载到内存的里面。 10000*100是指10000行指令*100行Add的指令总和的指令10000100是指10000行指令100行Add的指令总和的指令。内联展开会导致一个问题代码膨胀代码膨胀会导致可执行程序变大可执行程序变大加载到进程也会变大加载到内存导致内存变大。可执行程序就是安装包 • inline不建议声明和定义分离到两个文件分离分离会找这个链接的地址会导致链接错误。因为inline被展开就没有函数地址链接时会出现报错。链接就是声明的函数要找它的地址去其它文件找它的地址。 // F.h # include iostream using namespace std; inline void f ( int i); // F.cpp # include F.h void f ( int i) { cout i endl; } // text.cpp # include F.h int main () { // 链接错误⽆法解析的外部符号 void __cdecl f(int) (?fYAXHZ) f ( 10 ); return 0 ; } //那要怎么做呢 把// F.cpp这个文件中的fint i给注释掉 # include F.h //void f ( int i) //{ //cout i endl; //} 在// F.h中定义 inline void f ( int i) { cout i endl; } // 实现⼀个Add宏函数的常见问题 //#define Add(int a, int b) return a b; //#define Add(a, b) a b; //#define Add(a, b) (a b) // 正确的宏实现宏本质是一种替换 //宏函数坑很多但是由于替换机制调用函数时不用建立函数栈帧能做到提效的作用。 # define Add(a, b) ((a) (b)) // 为什么不能加分号 ? int main() { int ret Add(1,2);//这里是将a替换成1b替换成2//int ret Add(1,2 //在这种场景下不会有问题 cout Add(1,2) endl; //如果加分号在这种情况下就会报错,还有下面这种 if(Add(1,2)) { //... } cout ret endl; } // 为什么要加外⾯的括号 ? //有下面这种情况 int main() { int ret Add(1,2);//这里是将a替换成1b替换成2//int ret Add(1,2 cout Add(1,2)*3 endl; //cout (1) (2) * 3 endl; } // 为什么要加里面的括号 ? int main() { int x 1,y 2 Add(x y ,x | y); // - (x y x | y) } 8. nullptr C里面空指针都用NULLNULL实际是⼀个宏在传统的C头文件(stddef.h)中可以看到如下代码 #ifndef NULL #ifdef __cplusplus #define NULL 0//C #else #define NULL ((void *)0)//C语言 #endif #endif • C中NULL可能被定义为字面常量0或者C中被定义为无类型指针(void*)的常量。不论采取何种定义在使用空值的指针时都不可避免的会遇到⼀些麻烦本想通过f(NULL)调用指针版本的 f(int*)函数但是由于NULL被定义成0调用了f(int x)因此与程序的初衷相悖。f((void*)NULL); 调用会报错。 下面的代码我们普遍认为f(0)调用f(int x),f(NULL)调用f(int *ptr),但实际调用的都是f(int x)。 • C11中引入nullptrnullptr是⼀个特殊的关键字nullptr是⼀种特殊类型的字⾯量它可以转换 成任意其他类型的指针类型。使用nullptr定义空指针可以避免类型转换的问题因为nullptr只能被 隐式地转换为指针类型而不能被转换为整数类型。