网站怎么做页面解析跳转,网站建设微信开发,网站关键词seo怎么做,网页设计可以从事什么工作目录 一、引用
二、内联函数
三、auto关键字
四、指针空值nullptr 一、引用
引用不是新定义一个变量#xff0c;而是给已存在变量取了一个别名#xff0c;编译器不会为引用变量开辟内存空间#xff0c;它和它引用的变量共用同一块内存空间
类型引用变量名(对象名)…目录 一、引用
二、内联函数
三、auto关键字
四、指针空值nullptr 一、引用
引用不是新定义一个变量而是给已存在变量取了一个别名编译器不会为引用变量开辟内存空间它和它引用的变量共用同一块内存空间
类型引用变量名(对象名)引用实体 int a 0;int b a;cout a endl;cout b endl; 这其实就是给a变量取了一个b的别名二者同用一块空间
接下来是引用的一些实例说明
void Swap(int a, int b)
{int temp a;a b;b temp;
}
int main()
{int a 0;int b 1;Swap(a, b);
}
以前我们写Swap函数我们都需要去写指针然后把a和b的地址传过去但是我们用引用的话就可以和ab同用一块空间写起来的代码更加具有可读性和便捷
typedef struct ListNode{int val;struct ListNode* next;
}ListNode;
void PushBack(ListNode** phead, int x)
{ListNode* newnode;if (*phead NULL){*phead newnode;}else{//-----}
}
int main()
{ListNode* plist NULL;PushBack(plist, 1);
}
这是c语言的玩法我们在pushback的时候如果只传一个指针过去的话那么就跟传整型一样形参的改变并不影响实参所以我们要传指针的地址过去用二级指针这样理解起来是非常费劲的接下来我们看引用完优化的代码
typedef struct ListNode{int val;struct ListNode* next;
}ListNode;
void PushBack(ListNode* phead, int x)
{ListNode* newnode;if (phead NULL){phead newnode;}else{//-----}
}
int main()
{ListNode* plist NULL;PushBack(plist, 1);
}
这个我们就相当于给plist这个指针去取别名函数里面的改变就会直接影响到plist
有的上面也会这样写
typedef struct ListNode {int val;struct ListNode* next;
}ListNode,*PlistNode;
void PushBack(PlistNode phead, int x)
这种就是对结构体指针的typedef
2.引用特性
引用在定义时必须初始化
一个变量可以有多个引用
引用一旦引用一个实体再不能引用其他实体
int main()
{int a 0;int b a;int c a;int d b;
}
引用也可以是引用的别名本质都是同一个空间
作返回值
//传值返回
int Count()
{int n 0;n;return n;
}
int main()
{int ret Count();return 0;
}
出了作用域我们的n就销毁了所以不敢拿n返回所以会生成一个临时变量这个临时变量可能是拿寄存器充当或者其他等等
//传引用返回
int Count();
返回的是n的别名为什么返回n的别名Count不是被销毁了吗其实它就像你在酒店里面开了一个房间你不用就退房了然后你偷偷保留了一把钥匙你也不知道这个房间里会被别人使用过还是保持原状 这里打印的结果可能是1也可能是随机值具体要看编译器或者说操作系统如果这个栈帧没有清打印的结果就是1如果被清了就可能出现随机值
int main()
{int ret Count();cout ret endl;cout ret endl;return 0;
}
这段改的意思就是你ret就是n的别名 我们流插入两次就相当于调用了两次函数第一次我们可以调取到但是第二次向下开辟栈帧就可能比它打或者比它小就会对n这个变量进行覆盖我们第二次调用的就是覆盖以后的栈帧
如果我们定义一个很大的数组的话n在下面的话就可能不会被覆盖但是一般n都是在很前面的所以会出现随机值。
注意如果函数返回时除了函数作用域如果返回对象还在(还没还给系统则可以使用引用返回如果已经还给系统了则必须使用传值返回。
传引用传参(任何时候都可以)
1.提高效率
2.输出型参数(形参的修改影响实参)
传引用返回(出了函数作用域对象还在才可以用)
1.提高效率
2.修改返回对象
struct SeqList
{int a[10];int size;
};//C的接口设计//读取第i个位置的值
int SLAT(struct SeqList* ps, int i)
{assert(i ps-size);// ...return ps-a[i];
}/*修改第i个位置的值*/
void SLModify(struct SeqList* ps, int i, int x)
{assert(i ps-size);// ...ps-a[i] x;
}
这是c语言的程序设计但是我们用引用就能一个函数代码就解决了
CPP接口设计
读或者修改第i个位置的值
intSLAT(struct SeqList* ps, int i)
{assert(i ps-size);// ...return ps-a[i];
}
int SLAT(struct SeqList ps, int i)
{assert(i ps.size);// ...return ps.a[i];
}
int main()
{struct SeqList s;s.size 3;SLAT(s, 0) 10;cout SLAT(s, 0) endl;return 0;
}
我返回数组第0个位置的别名我修改这个别名就是在修改这个数组的值
如果不加引用呢返回的就是临时对象而临时对象具有常性不能修改
接下来我们借用到一下类里面的知识
struct Seqlist
{//成员函数int at(int i){assert(i 10);return a[i];}//成员变量int a[10];
};
int main()
{Seqlist s2;for (size_t i 0; i 10; i){s2.at(i) i;}for (size_t i 0; i 10; i){cout s2.at(i) endl;}
}
常引用
int main()
{const int a 0;int b a;return 0;
}
这段代码是过不去的为什么呢举个例子你是一个囚犯你可以在你所在的范围内运动但是给你取了一个别名的时候你就不是这个囚犯了吗你就可以自由运动了吗
这本质上是权限的一种放大
在引用的过程中
权限可以平移
const int b a;
权限可以缩小 int x 0;const int y x;
权限不能放大
int ba;这个是可以的这是一种赋值把a的值赋值给bb的修改并不会影响a
int main()
{int i 0;double d i;
}
这个是中间生成一个double的临时变量
所以改成这样子就不行了
double d i;
因为临时变量具有常性跟上面举的例子是一样的
所以加const就行了
const double d i;
const 引用会延长这个对象的生命周期直到你这个引用变量被销毁为止
引用和指针的区别
int main()
{int a 0;int* p1 a;int ret a;(*p1);ret;return 0;
} 我们都知道指针是开空间的但是引用开不开空间呢引用从语法上讲是不开空间的但从下层来看我们通过上面的对比上可以看到其实引用的本质就是指针
为什么那块销毁了我们还能访问它因为引用的底层就是指针
引用和指针的不同点
1.引用概念上定义一个变量的别名指针存储一个变量地址
2.引用在定义时必须初始化指针没有要求
3.引用在初始化时引用一个实体后就不能再引用其他实体而指针可以在任何时候指向任何一个同类型实体
4.没有NULL引用但有NULL指针
5.在sizeof中含义不同引用结果为引用类型的大小但指针始终时地址空间所占字节个数(32位平台下占4个字节)
6.引用自加即引用的实体增加1指针自加即指针向后偏移一个类型的大小
7.有多级指针但没有多级引用
8.访问实体方式不同指针需要显式解引用引用编译器自己处理
9.引用比指针使用起来相对更安全
二、内联函数
概念
以inline修饰的函数叫做内联函数编译时C编译器会在调用内联函数的地方展开没有函数调用建立栈帧的开销内联函数提升程序运行的效率
这个其实是补C语言宏的一个坑
首先我们来看这个
#define Add(a,b) ab;
int main()
{Add(1, 2);printf(%d\n, Add(1, 2));return 0;
}
宏的后面是不能加的因为宏是一模一样的替换到后面那个你如果12替换到下面去那么肯定是会报错的就会变成12
#define Add(a,b) ab
int main()
{Add(1, 2);printf(%d\n, Add(1, 2)*3);return 0;
}
这种的话优先级就有问题变成12*3
#define Add(a,b) (ab)
这种的话普通场景是没有问题的但是特殊场景就会有问题了 int a 1, b 2;Add(a | b, a b);//(a | b a b)
这样子号的运算符的优先级就在其他上面
#define Add(a,b) ((a)(b))写成这样子才会对我们看宏其实是有许多缺点的
1.容易出错语法坑很多
2.不能调试
3.没有类型安全的检查
我们写一个加法函数都比这个加法宏简单很多
优点
1.没有类型的严格限制
2.针对频繁调用小函数不需要再建立栈帧提高了效率
C允许把一个函数定义成内联函数
为了减少栈帧的建立开发出了内联函数
inline int Add(int x, int y)
{return x y;
}
特性inline是一种以空间换时间的做法如果编译器将函数当成内联函数处理在编译阶段会用函数体替换函数调用缺陷可能会使目标文件变大优势少了调用开销提高程序运行效率
2.inline对于编译器而言只是一个建议不同编译器关于inline实现机制可能不同一般建议将函数规模较小(即函数不是很长)具体没有准确的说法取决于编译器内部实现、不是递归且频繁调用的函数采用inline修饰否则编译器会忽略inlinetexing
3.inline不建议声明和定义分离分离会导致链接错误。因为inline被展开就没有函数地址了链接就会找不到 我们在调用一个函数建立栈帧的时候就会call这个函数
内联就是在需要的地方直接展开不会建立栈帧
func 100行
100个位置调用
是inline10000行就是在一百个地方直接展开
不是inline200行
不是内联这个函数就是一个调用那就是100行放在一个地方就是一个指令调用的地方只需要一行指令就可以就是call Func0x31312然后再jump过去就可以调用这个函数了
所以就会引发一个问题叫作代码膨胀就会导致你的程序变大
所以比较短的它才会展开
声明和定义分离也会出问题 它会报链接错误它会在链接的时候根据函数名修饰规则去找这个函数的地址找不到就会出链接错误。
但是奇怪的是这样子就可以成功了
#include Test.h
void f(int i)
{cout f(int i) i endl;
}
void fx()
{f(1);
}
#include iostream
using namespace std;
inline void f(int i);
void fx(); 我们这是间接调用的fx()不是内联居然就可以调动的f这个函数肯定是在的要不然fx这个函数也不能调不到的
因为内联函数直接在使用的地方展开了我们就没有必要生成一堆指令建立函数的栈帧把它们的地址放入符号表就没有地址了
那为什么fx能展开呢我们仔细看看fx()既包含声明又包含它的定义所以直接就在使用的地方展开了
所以内联函数不要声明和定义分离 有人会觉得f会不会跟别的文件里面的重合呢就是我这个.cpp文件里面有另一个.cpp文件里面也有合并的时候会不会展开冲突是不会的因为它不生成指令它在调用的地方就展开了
我在其他文件里面只有声明没有定义怎么能够展开内联函数呢
三、auto关键字
int main()
{int a 0;auto b a;auto c a;auto d a;
}
可以自动推导类型
普通场景没有价值
类型很长就有价值这在以后的学习以后可以知道 std::vectorstd::string::iterator it v.begin();auto it v.begin();
比如说上面这么长一串代码可以简化成下面这样
auto不能推导的场景
auto不能作为函数的参数
auto不能直接用来声明数组
C有一种东西可以帮助我们看它的类型
cout typeid(c).name() endl;
cout typeid(d).name() endl; 范围for的语法
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;
}
我们可以看到C读写一段数组代码是很长的
所以C引入了范围for这种语法
for (auto e : array)
{cout e ;
} 在以后的容器中也可以用到
它会依次取数组中的数据赋值给e
自行判断结束
自动迭代()
注意赋值给e改变e的值不会影响到数组里面的值所以这就跟上面的知识串起来了我们要用到引用这种东西
for (auto e : array)
{e * 2;cout e ;
} 注意不能用指针只能用引用类型是不会匹配的
void TestFor(int array[])
{for(autoe:array)cout e ;
}
这个地方是不能这么玩的范围for针对的是数组名而你的参数却是指针
四、指针空值nullptr
void f(int i)
{cout f(int) endl;
}void f(int* p)
{cout f(int*) endl;
}int main()
{f(0);f(NULL);return 0;
}
这个实际上都会匹配到第一个去C把null define成00这个变量的值默认是整型
所以引入了nullptr这个nullptr的类型是
(void*)nullptr C入门就到这里结束了下一站到类和对象有写的不好的望大家指出