笔记(C++篇)—— Day 11
1.引用
1.1 引用的定义
引用,不是定义一个新的变量,而是给已经存在的变量取一个别名。
编译器不会为引用变量开辟内存空间,它和它引用的变量共用一块内存空间。
代码示例:
int main()
{int i = 1;int& j = i;cout << &i << endl;cout << &j << endl;++j;int& k = j;return 0;
}
1.2 引用的特性
①引用在定义时必须初始化
②一个变量可以有多个引用
③引用一旦引用一个实体,再不能引用其他实体
1.3 引用的使用
①主要是引用传参和传引用返回中减少拷贝提高效率,和改变引用对象时同时改变被引用对 象。
②在链表,树和节点定义位置,只能使用指针
(C++的引用无法改变指向,在这些问题中一定会存在指向改变的情况)
③引用传参和指针传参是类似的,不过引用传参更加方便一些
④传值返回会产生临时对象作为函数调用表达式的返回值,传引用返回是返回返回对象的引 用(别名),从而提高效率,同时可以改变返回对象。
(传值返回会产生临时变量,临时对象不能被修改)
(传应用返回就是返回别名)
⑤引用和指针在实践中相辅相成,功能具有重叠性,但是各有特点,互相不可替代。
变化不大的示例:
void Swap(int* rx, int* ry)
{int tmp = *rx;*rx = *ry;*ry = tmp;
}void Swap(int& rx, int& ry)
{int tmp = rx;rx = ry;ry = tmp;
}int main()
{int x = 0, y = 1;cout << x << " " << y << endl;Swap(&x, &y);cout << x << " " << y << endl;Swap(x, y);cout << x << " " << y << endl;return 0;
}
传引用返回示例:
//传引用返回
int& func()
{int ret = 0;//...return ret;
}int main()
{int x = func();cout << x << endl;return 0;
}
(本质相当于返回了ret 的别名)
(在前面的学习中,已经学过越界不一定会报错)
int& func()
{int ret = 0;//...return ret;
}int& fun2()
{int y = 123;//...return y;
}
int main()
{int& x = func();//相当于x是ret的别名cout << x << endl;fun2();cout << x << endl;return 0;
}
上述代码的运行结果是:
原因是什么?
--> 与栈帧的建立和销毁的知识相关
那么怎样才是正确的呢?修改代码见下述代码:
int& func()
{static int ret = 0;//...return ret;
}
(这样修改过后,ret所在的栈帧还没有销毁,则x的值一直都会是0)
int main()
{int i = 0;//语法上,引用不开空间,指针开空间int& r1 = i;int* p = &i;r1++;(*p)++;return 0;
}
(语法上,引用不开空间,但是在底层会发现和语法不一样,这里的语法仅仅是为了更好的理解)
1.4 const引用
①可以引用一个const对象,但是必须用const引用。
const引用也可以引用普通对象,因为对象的访问权限在引用过程中可以缩小,但是不能 放大。
②临时对象,就是编译器需要一个空间暂存表达式的求职结果时临时创建的一个未命名的对 象,C++中把这个未命名对象叫做临时对象。
代码示例如下:
int main()
{const int a = 0;//权限放大int& b = a;//a取了一个别名,叫做breturn 0;
}
(上述代码中,是错误的,涉及到了权限的放大,a的数值不能改变,b作为a的别名,数值却可以改变,相当于权限的放大,因此该代码是错误的)
代码修改如下:
int main()
{const int a = 0;const int& b = a;//a取了一个别名,叫做breturn 0;
}
(a不能修改,b也不能修改,那么这个代码就会正确)
下面的代码是正确的:
int main()
{int c = 0;const int& d = c;return 0;
}
(上述的权限缩小了,正确)
权限不可以放大,但是可以缩小。
int main()
{const int a = 0;int e = a;return 0;
}
(这个不涉及权限的放大和缩小,仅仅是把a的值赋给e)
(上述引用的用法在指针中也同样适用)
int main()
{int i = 1;double d = i;int p = (int)&i;double& rd = i;//不可以const double& rd = i;//可以return 0;
}
(在上述的代码中,double的第一个不可以进行转化,但是在其前面加上const后就可以进行转换,原因——>前三行代码中,i的值不能强转为double类型,而是先产生一个临时变量,先给那个临时变量,再进行类型的转换;临时变量具有常性,即不能轻易修改;则没加const不能进行转换的原因是,涉及到了权限的放大,rd在其中引用的是临时对象。)
1.5 指针和引用的关系
①语法上,引用是一个变量的取别名,不开空间;指针是存储一个变量地址,开空间。
②引用在定义时必须初始化;指针建议初始化,但不是必须的。
③引用在初始化后引用一个对象后,不能再引用其他的对象;指针可以。
④引用可以直接访问指向对象;指针需要解引用才可以。
⑤sizeof中含义不同,引用结果为引用类型的大小;指针始终是地址空间的所占字节个数。
⑥指针很容易出现空指针和野指针的问题;引用很少出现,更安全一些。
2.inline
①用inline修饰的函数就叫做内联函数,编译时,C++编译器会在调用的地方展开内联函数, 这样调用内联函数就不需要建立栈帧了,就可以提高效率。
(所以,在这个地方就不属于函数的调用了,而是展开。)
②Inline对于编译器而言,只是一个建议;且更适用于短小函数。
(加了inline编译器也可以选择在调用的地方不展开,不同编译器关于Inline什么时候展开
的情况各不相同。inline适用于频繁调用的短小函数,对于递归函数和代码相对多一点的
函数,加上Inline也会被编译器忽略。)
③C语言实现宏函数也会在预处理时替换展开,但是宏函数实现很复杂,很容易出错,且不 方便调试,C++设计inline的目的就是替代C的宏函数。
④vs编译器debug版本下面默认是不展开inline的,这样方便调试。debug版本想展开需要设 置。
⑤inline不建议声明和定义分离到两个文件中,分离会导致链接错误。因为inline被展开,就
没有函数地址,链接时会出现报错。
(所以,inline函数直接定义到.h文件中)
宏函数的代码示例:
//实现一个ADD宏函数的常见问题
#define ADD(int a, int b) return a + b;
//宏不是为了写出一个函数
#define ADD(a, b) a + b;
//宏是一种替换机制,但是在某些场景下,不可以加分号
#define ADD(a, b) (a + b)
//在某些场景下,a和b是表达式,并且表达式中的符号的优先级比+-低,那么就会出现问题
(宏的坑有很多,在C++中,通常会使用const enum和Inline来替代宏)
#define ADD(a, b) ((a) + (b))
(上述是综合各种出现的问题,写出来的正确的表达方法)
(虽然宏具有缺点,也具有其独特的优点。高频调用小函数,可以写成宏函数,可以提高效率,预处理阶段宏会替换,提高效率,不建立栈帧)
inline int ADD(int x, int y)
{return x + y;
}int main()
{int ret = ADD(1, 2) * 3;cout << ret << endl;int x = 0, y = 1;ADD(x | y, x & y);return 0;
}
(用内联函数就不会出现上述宏所出现的问题)
3.nullptr
NULL实际上是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:
在该情况下,会出现问题:
void f(int x)
{cout << "f(int x)" << endl;
}void f(int* ptr)
{cout << "f(int* ptr)" << endl;
}int main()
{f(0);f(NULL);int* p1 = NULL;char* p2 = NULL;return 0;
}
运行结果为:
(两个调用的函数是一样的,因此,出现了问题。)
①C++中NULL可能被定义为字面常量0,或者C中被定义为无类型指针(void*)的常量。无
论采用何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,本想通过f(NULL)
调用指针版本的f(int*)函数,但是由于NULL被定义为了0,调用了f(int x),因此与程序的初
衷相悖。f((void*)NULL);调用会报错。
②C++11中,引入了nullptr,nullptr是一个特殊的关键字,nullptr是一种特殊类型的字面量,
它可以转换成任意其他类型的指针类型。使用nullptr定义空指针可以避免类型转换的问题,
因为nullptr只能被隐式地转换为指针变量,而不能被转换为整数类型。
(因此,C++中定义空指针都用nullptr)