两点了啊哈,手机玩够了,开始整活。
给霍格沃兹打工的老猫头鹰了。
听说学道很有名,·且来打个照面,读来看看有不懂的就查一查,学到学道一点算一点。
斜体下划线是回答,其他的是背景、解释、分析甚至吐槽。
目录
p11 全局变量可以和局部变量重名?
记得在以前面过程编程的时候,数据都是能全局就全局,因为在函数中可以随意修改,不用考虑传值的麻烦。但现在工程类的代码要求保护数据,全局变量在红书里被写作“建议在不必要时不要使用”,理由是占用储存单元、代码通用性降低和清晰度下降。
这些都好理解,所以能够在函数中改变值的全局变量怎么能和函数中的局部变量重名呢。
查了一下,原来是,在同一个源文件中,全局变量与局部变量重名,则在局部变量作用范围,全局变量被屏蔽,即不起作用,可以使用局部变量。
p12作用域运算符::
喔,作用域运算符还可以用来引出全局变量。
说实话我对作用域运算符知之甚少,第一次遇到记得是如果不写usingnamespacestd;需要在语句前用std::。
于是查一下资料。
第一个人讲:作用域运算符分为全局作用运算符、类作用运算符和命名空间运算符,bulabula
第二个人讲:(这个人说话怎么跟我一个调调)
C++使用::运算符搞定很多东西,其中最基本的一个用处就是用来决议多继承中的重名成员。::运算符的本质是:左操作数是一个scope,右操作数是这个scope内的名字,可以是 scope,class,member或者function,variable等等,从左操作数指定的scope中找到有操作数这个实体。看起来非常优雅,不是么?可是,它有好几个缺陷。
好了缺陷我们姑且先不管,作用域运算符到这里已经讲很清楚了。
p12变量定义前的修饰符
来来来,聊一聊修饰符,我的意思是系统地聊一聊。
他这里说的修饰符,在网上更广泛的叫法是“储存类别说明符”,指的是auto,register,extern,static。
1)auto(这部分完全超纲,不要硬刚)
在c语言中(和C++11以前,也就是学道中所谓的“老版本C++”),auto就是块或者函数头中变量的初始自动状态,即不作声明,我们的变量就是可以自动完成周期的。这时auto的作用可以是
为了表明有意覆盖一个外部变量定义,或者强调不要把该变量改为其他存储类别
在C++11以后,auto有了全新而强大的功能,学道可能为了防止大家走火入魔所以没讲一笔带过。我不怕哈,我来解释一下,胆小的可以捂上耳朵了。
auto可以再声明变量时根据变量初始值的类型自动为此变量选择匹配的类型。这是auto就不是一个说明符了,而是和数据类型有等价的地位,可以直接用它来定义变量。auto a = 1; auto b = 1.0191019;atuo c ("YewLi_shuaideyimiao")都是合法的,注意,这仨变量不能写一个auto下就成,一个auto后面得是一样的类型才成。
但是嘞,不应该向上面这样滥用auto,这种简单显然的应该写出它的数据类型。auto应该用于“类型冗长复杂变量使用的单一范围”,如下例:
std::vector<int>vect;
for(auto it = vect.begin();it != vect.end();it++){
//it 这里的类型是std::vector<int>::iterator
std::cin>>*it;
}
讲真,写到这里我有点,你要说这个我可就不困了的感觉,手动狗头。我爱所有人,但可能最爱编译器,以及电脑旁边这杯自由古巴。
auto差不多就这样,但不能用它来处理精度问题哈,让他来选择使用long,long long以及unsigned long long 之类的就是你的偷懒和它的力所不能及了。
2)register寄存器变量
寄存器变量存储在CPU的寄存器,速度是很快的哟,值得注意的是,寄存器变量也是块作用域,无链接和自动存储期。这么好的东西自然个数有限,多出的会用auto储存。
3)static静态变量
静态的意思是该变量在内存中原地不动,并不是指值不变。且具有文件作用域的自动具有静态存储器。要注意的是,自己创建具有静态存储期的变量在离开它们所在函数后,这些变量不会消失。
(顺嘴说一句,讲这一段我参考的博客那哥们话比我还多还碎,但还挺有意思,指路我参考博客里第一个网址)
静态变量就是在函数结束后不会被释放,数值一直保持的变量,经典例子就是递归函数求阶乘。特别的,静态变量的初始化值为0,不是任意值。
4)extern
extern int num是指示编译器去别处查询其定义,这里的别处往往指另一个文件。在这里对num赋值是不合法的,应该去别的文件中查找到值后使用该值。
然后你可能会问(你可能不会问,是我自己问的),这东西和我们学过的const,static很像啊,不是一码事吗。
嘿,真不好说。(您瞧我这相声听多了的)下面为了目录制作方便,另开一个分栏。
p12变量定义前的修饰符(续)
上面那四个是尊重了学道给出的四个“修饰符”,其实这块儿的名字叫得有点乱。u1s1,这里的名字理了还是乱的,大家会建议你搞清楚具体用法就行,别太在意和翻译还有一定关系的汉语类别名称。以后这里可以根据外文考据一下,今天就没有必要了。
类型区分符(type-specifier),一般是void, int, short, long, float, double ,结构体类型,枚举类型,typedef类型。
类型修饰符signed, unsigned, long, short
类型限定符(type-qualifier),又被称作读取限定符,一般是const 或者voliate。
大概有这些:auto,static,register, extern, const, (explicit),(mutable) , volatile,(virtual),(inline),restrict。括号里的是自定义类类型定义过程中特有的修饰符。
顾名思义,前四个大概是解决住房问题(储存类别)的,后面这些解决实际工作(类型限定)的。其实能分得开吗,不太能说实话。下面挑着讲下后面几个。
总说规则:a. 类型限定符和类型指定符的顺序可交换,为了便于阅读类型限定符写前面;b. 派生类型不会继承类型限定符;c. 同一个类型限定符同时出现多次,相当于只出现一次(这句话的意思你要是闲的来着可以写const const const const int a)
分讲细则:
1)const
const 比较准确的含义是"只读",而不是“常量”,“常量”在 C 语言中有特定的含义,与 const 无关。const 的好处在于
a. 优化 当程序中出现 const int i = 0
这样的代码时,它表示程序不准备修改对象 i 的存储值,编译器可以根据这种意图提供适当的优化。
比如,编译器可以在第一次访问对象 i 时将它缓存起来,以后只需要使用这个缓存值而不需要浪费时间重新读取。
b. 安全 编译器会检查代码是否违反了 const 的限定规则,对变量做了不适当的修改操作。但是用 const 限定的对象并非不可改写的,可以通过强制类型转换来绕过这种限制。
例如:
int x = 0;
const int *p = &x;
(*p)++; //S1
(*(int*)p)++; //S2
//S1 是非法的,S2 是合法的,S2 的做法是非常危险的,其后果未定义。
c.作用范围 const 限定符有其作用范围,如果多段代码共享同一个对象,或者多个线程共享同一个对象,那么这个对象可能在一个范围内是 const 限定的,在另一个范围内则不是。
例如:
void f(const char* c){
*c = 'x'; //S1,非法
}
void g(void){
char a[3];
f(a);
}//上述数组 a 的元素在 g() 中是可修改的,在 f() 中是只读的, S1 代码段的操作是非法的。
以上内容引用修改自一个嘴不碎还写的真好的博主,指路链接第四个。
2)volatile
与 const 相反,volatile 的含义是告诉 C 实现,对象的值会改变,并且是以不受控制的方式改变。如果一个类型是 “volatile 限定的类型”,则意味着该类型所定义的对象,它的值不单单会被当前程序的代码修改,还可能潜在地被其他程序或代码修改。因此,编译器不能在编译时对访问该对象的代码做优化处理,对这种对象的处理不能依赖于缓存特性。
例如,它可能对应一个硬件的端口,或者几个程序或线程公用的存储位置等等。
int i;
interrupt_handler(){
i = 1;
}
void function(){
i = 0;
while (0 == i);
}
/*虽然本意是在中断服务函数中将 i 赋值为 1,但实际上 function 函数中的 while 循环可能永远不会退出。
因为编译器看到在 while 循环前 i 被赋值为 0,while(0 == i) 的条件比较结果肯定为 1,如果打开了编译器的优化选项,while(0 == i) 会被优化为 while(1)。
解决方案就是在声明 i 的时候加入 volatile 限定符:volatile int i;这样编译器就不会做出不适当的优化。*/
这位博主,说得好哇。
3)restrict
restrict 限定符仅适用于指针类型。如果一个指针类型是 restrict 限定的,则在一个代码块内,所有到这个对象的引用必须直接或者间接通过这个指针进行。
基于上述保证的条件,编译器就可以在代码块的开始处安全地缓存“该指针所指向的对象”的值,读取和更新操作只针对这个缓存值进行。在退出代码块之前,再将缓存的值写回到指针所指向的对象。
如果没有这个限定符,则意味着在当前块内,还可能存在着其他指向这个对象的指针,因此,缓存对象的值是不安全的。
以下两个代码段:
void f1(int * p1, int * p2, int n)
{
for (int i = 0; i < n; i++) {
*p1 ++;
*p2 ++
}
}
void f2(int * restrict p1, int * restrict p2, int n)
{
for (int i = 0; i < n; i++) {
*p1 ++;
*p2 ++
}
}
//用相同的方法调用上述f1,f2
int i = 0;
f1(&i,&i,45);或f2(&i,&i,45);
f1 调用完成后,i 的值是 90;f2 调用完成后,i 的值是 45
原因在于,在 f2 中,参数 p1 和 p2 都被声明为 restrict 限定,但调用时他们又指向了同一个对象,这违背了 restrict 的约定。
因此在 f2 内部,for 循环里的两个累加过程都认为只有自己在改变对象的值,因此都使用了缓存值,而不是实际访问对象的值。当退出 for 循环时他们各自的值都是 45,退出 f2 函数前,这两个缓存值被各自刷新到 i 中,导致 i 的值是 45。
这里没完,但太长了我缓口气,关于类的我们总结在下一篇。
参考博客和网站:https://www.cnblogs.com/xingchenshuai/p/13724240.html
https://blog.youkuaiyun.com/lanmeng_smile/article/details/28277407
https://www.cnblogs.com/gctech/p/13451472.html
https://blog.youkuaiyun.com/mrwenzhi/article/details/76649115