C语言常见关键字
写在前面
这个博客是结合C语言深度解剖这本书和我以前学的知识综合而成的,我希望可以更见详细的谈一下C语言的关键字,内容有点多,有错误还请斧正.
常见关键字
下面我们说下C语言的关键字,所谓的关键字是指具有特定功能的单词,我们可以使用关键字来帮助我们完成不同的事物.C语言给我们内置了下面这么多的关键字,共32个.
auto break case char const continue default do double else enum
extern float for goto if int long register return short signed
sizeof static struct switch typedef union unsigned void volatile while
typedef
这个关键字是很简单的,我们类型名字起外号,就像下面的用法.
typedef int zhengxing;int main()
{zhengxing a = 10;return 0;
}
注意,我们这里起外号之后,使用的方法和原本的是没有任何区别的,这在内置类型或许还不太明显,主要是内置类型的用法是在是太简单了,再后面的额函数指针和自定义类型就非常厉害了,这我们后面再说.
define
这个也是我们C语言经常使用的关键字,它是定义一个宏.我们有下面的一种情况.
int main()
{char arr[100] = { '\0' };return 0;
}
可是后面你发现我们给的空间是在是太小了,这里就要求我们扩大空间,也就是找源码一点一点改,现在我们这里代码很少,因此一眼就可以看出,但是如果代码多呢?这里就要求我们是用宏.
#define NUM 100int main()
{char arr[NUM] = { '\0' };return 0;
}
注意宏的作用就是替换所谓的替换就是我们程序在运行之前编译器会自动的把宏的实际内容替换进去,因此我们需要把他和typedef比较一下.我们看一下下面的情况.
int main()
{int* p, a;return 0;
}
这是由于*我们可以认为之和p结合,这里有涉及到代码规范的问题.如果我们使用宏,会出现下面的情况,但是typedef不会.
#include <stdio.h>
typedef int* Ptr;
#define INT_P int*int main()
{INT_P p1, a;Ptr p2, b;return 0;
}
auto
一般在代码块中定义的变量,即局部变量,默认都是auto修饰的,不过一般省略,并不是默认的所有变量都是auto,auto一般只用来修饰局部变量,局部变量,自动变量,临时变量,都是一回事。我们统称局部变量.
#include<stdio.h>
#include<windows.h>int main()
{auto int a = 10; // a --- 局部变量printf("%d\n", a);system("pause");return 0;
}
如果我们省略了变量的类型.只保留auto关键字,会被默认是int类型
#include<stdio.h>
#include<windows.h>int main()
{auto a = 10; auto b = 10.0;printf("%d\n", b);printf("%d\n", b);system("pause");return 0;
}
那么我想问的是上面我们说的是auto是局部变量,那么请问我们是不是可以用auto来修饰全局变量?注意这是不行的
sizeof
记住sizeof 是一个关键字,不是函数!不是函数!!! sizeof的单位是字节 (Byte).一个字节有8个二进制位(bit)组成。 注意:sizeof计算数值类型时一定要带括号
#include<stdio.h>
#include<windows.h>int main()
{printf("%d\n",sizeof(char));printf("%d\n",sizeof(short));printf("%d\n",sizeof(int));printf("%d\n",sizeof(long));printf("%d\n",sizeof(long long));printf("%d\n",sizeof(double));system("pause");return 0;
}
下面我们补充点东西,sizeof可以也计算常量 表达式的大小
int main()
{int i = 10;printf("%d\n",sizeof(i)); //带括号printf("%d\n",sizeof i); //可以不带括号//注意 如果是下面这种情况,一定要带括号int j = 20;printf("%d\n",sizeof (i + j)); system("pause");return 0;
}
sizeof计算的是开辟空间的大小,像 字符‘\0’ 也是会计算上去的
#include<stdio.h>
#include<string.h>
#include<windows.h>
int main
{char arr[12] = "hello world";printf("%d\n", strlen(arr));printf("%d\n", sizeof(arr));system("pause");return 0;
}
extern
这里我们需要引入一些东西,我们已经知道变量的存在了,但是有的问题还是需要我们进行讨论的.
什么是变量
变量是程序在运行时在内存中开辟特定大小的空间,用来保存数据。
我们这里抓取两个关键字,一个是运行,一个是内存.先来解释运行.
所谓饿饿运行就是程序跑起来的时候,只有这时候我们的空间才会被开辟,如没有运行那么所谓的变量就是一个个字符,只有书面意思,没有实际的意思.
那么变量为何要在内存开辟呢?他不会去硬盘中开辟吗?因为变量是程序在运行的时候开辟的,程序在运行后已经被加载到内存。也就是说,当我们要定义变量的时候,程序已经在内存里了,所以变量只能在内存中开辟空间。
换言之,虽然我们已经在编译器中定义了一个变量,但是这个变量并不会在我们写下这句代码的时候就开辟内存,而是必须要整个程序都加载到内存之后才会在内存中开辟空间。
为何要有变量
计算机的诞生是为了解决人计算能力不足的问题而诞生的。即,计算机是为了进行计算的。 而计算,就需要数据。 而要计算,任何一个时刻,不是所有的数据都要立马被计算。 如同:要吃饭,不是所有的饭菜都要立马被你吃掉。饭要一口一口吃,那么你还没有吃到的饭菜,就需要暂时放在盘子里。 这里的盘子,就如同变量,饭菜如同变量里面的数据。 换句话说,为何需要变量?因为有数据需要暂时被保存起来,等待后续处理。 那么,为什么吃饭要盘子?我想吃一口菜了,直接去锅里找不行吗?当然行,但是效率低。 因为我们吃饭的地方,和做饭的地方,是比较"远"的。
变量定义
类型 变量名 = 默认值
int main()
{int a = 10;return 0;
}
我们把在变量定义出来的那一刻赋值称之初始化,前面我们说了变量出来我们最好要初始化,这里说一下对数组的不完全初始化.如果我们数组没有初始化完整,后面会自动补成0.
int main()
{int arr[10] = { 0 };return 0;
}
变量的声明
不知道大家对于extern的了解怎么样?知道什么是变量的声明吗?我们如何使用其他文件里面的全局变量?知道extern的作用了,它是申明一个变量或者是函数我们来看它的作用吧.
声明全局变量.我们在test.c中定义一个全局变量,如何在main.c中使用?
//test.c
int g_val = 10;//main.c
#include <stdio.h>
extern int g_val; //这里会告诉编译器 g_val是一个全局变量.
int main()
{printf("%d", g_val);return 0;
}
这是由于VS再编译程序中会把所有的源文件编译成.o文件,这里涉及到编译原理的知识,我们先不谈,记住这样的现象就可以了.
这里有一个问题,要是我们再次在main.c里面定义一个全局变量g_val会怎么样?
//main.c
#include <stdio.h>
extern int g_val;
int g_val = 20;
int main()
{printf("%d", g_val);return 0;
}
我们发现运行时过不了,编译器在链接时寻找变量,它不能够区分这两g_val有什么不同.那么我们在两个文件中声明全局变量时给它赋值会怎么样?
//main.c
extern int g_val = 20;int main()
{printf("%d", g_val);return 0;
}
这个是不可能过的,extern的作用是声明变量,它不会开辟空间,我们初始化或者是赋值都是把数据放到开辟的内存当中,一定会报错.可是我们有疑惑了,按照道理而言,下面是存在一个文件中的,下面的代码应该是错误的,为什么会跑过?
#include <stdio.h>
extern int g_val = 10;int main()
{printf("%d", g_val);return 0;
}
声明函数.所谓的声明函数也是一样的,这里我们就不谈了,主要是现在很少用
定义与声明的本质
定义变量的本质,程序在运行时为其变量或函数开辟内存空间,用来保存数据。至于初始化,则是完成内存空间开辟,为其开辟的内存填指定的值。变量只能定义一次,但是一个变量可以声明多次,声明只是告诉了编译器存在这么一个变量或者函数,这个函数或者变量在其他的位置定义过了,所以在这个过程中没有为其再次分配内存.他们最大的区别就是有无分配内存。
static
关于staic关键字,我有很多想和大家分享的,它是在太让我们忽略了,即使是现在我还需要借助我以前的笔记来写这篇博客.我脑子就记住了一个,static修饰局部变量改变它的生命周期,不改变作用域.下面是我总结的一些static的作用.
- 修饰局部变量
- 修饰全局变量
- 修饰函数
修饰局部变量
static修饰局部变量改变它的生命周期,不改变作用域,我们先来看看代码和现象.
void fun()
{int a = 1;//不用 static修饰a++;printf("%d ",a);
}int main()
{int i = 0;while (i < 10){fun();i++;}printf("\n");return 0;
}
下面就是static的不同.
void fun()
{static int a = 1; // static修饰,在程序运行前只进行一次初始化a++;printf("%d ",a);
}int main()
{int i = 0;while (i < 10){fun();i++;}printf("\n");return 0;
}
从现象我们可以看出,static 修饰的变量的 空间没有被销毁,否则不会打印出3 4 5… 我们证明一下,看看打印出来a的值是
可以看出,a的值是确定值,其地址没有被销毁,那么我们可以得到static改变的局部变量的生命周期,它是在整个程序中都有效的.那么作用域变了没有? ------- 作用域没有改变.
为什么static可以改变局部变量的生命周期?static修饰的局部变量,会在全局数据区或者静态数据区开辟空间(编译器的不同),这就造成了static可以改变局部变量的生命周期。详细的可以看一下C程序地址空间
为什么函数和全局变量可以跨文件访问
在谈这个之前,我们需要说一说多文件,为何我们要定义几个文件,我们可以试想一下这样的场景,我们写的函数很多,当我们使用函数的时候发现要找好久,有时还不知道函数的参数和返回值,我们是不是可以定义一个头文件,把自己的写的函数都声明出来.
- .h:我们称之为头文件,一般包含函数声明,变量声明,宏定义,头文件等内容(header)
- .c: 我们称之为源文件,一般包含函数实现,变量定义等
我们写的大型项目一般都是多文件项目,文件与文件之间一定要可以进行跨文件访问,否则,我们不能跨文件,那么“交互”的成本就比较高。但总有些代码需要隐藏,所以出现了static这个关键字。
修饰全局变量
static修饰的全局变量只能在本文件中内被访问,不能被外部文件直接访问.
全局变量拥有外部链接属性,被static修饰后,外部链接属性好像消失了
修饰函数
先不来谈这个,我们先看下这种情况,我们在test.c里面定义一个函数,在main.c里面直接调用,什么都不做,这个代码会不会报错.
//test.c
void show()
{printf("你可调用到我\n");
}//main.c#include <stdio.h>int main()
{show();return 0;
}
我在mian.c里面都没有声明这个函数,为毛还会出现正确的关键字?这是怎么回事.这是由于函数具有外部链接属性,当我们连接时,编译器会自动去寻找这个函数
static修饰的函数和全局变量一样,只能在本文件中内被访问,不能被外部文件直接访问
可是当我们使用static修饰之后我们就发现无法调用了,这里主要是为了封装一些只在本文件使用的接口.
那被static修饰的函数如何可以间接访问在static修饰的函数的文件内,可以再写一个函数调用被static修饰的函数,在外部文件调用该函数,就可以间接调用static修饰的函数了
下面我给一个总结.
- static 修饰局部变量,改变的是生命周期,不改变作用域。
- static修饰函数,目的在于封装,提高代码的安全性。使用户只能使用该文件,但是不能随意修改里面的代码,static提供项目维护、安全保护。
register
我们需要了解一些计算机组成的知识,根据机械原理,较大的存储设备要比较小存储设备运行的慢,而快速设备的造价要高得多,所以系统设计者采用高速缓存存储器作为集结区域,用于处理器存放近期可能用到的信息,存储器结构层次已经深入人心。
距离CPU越近的存储硬件,运行速度越快,CPU内集成了一组存储硬件,这就是寄存器。
register修饰的变量,就是尽可能的将期放入CPU寄存区中,从而达到提高效率的目的
1.register修饰的变量最好是局部变量
2.register修饰变量只是请求存放在寄存器中,当然也有放不进去情况,寄存器的数量是 有限的。
3.register修饰的变量,不能对该变量取地址
#include<stdio.h>
#include<windows.h>int main()
{register int a = 10; int *p = &a; printf("&a = %p\n",p);system("pause");return 0;
}
VS2013 报错 :1 error C2103: 寄存器变量上的“&”
gcc 报错 :错误:要求寄存器变量‘a’的地址。
void
我们先来谈谈在你平常遇到void的方式都有哪几种?一般情况而言,有下面连种方式。
- 告知编译器这个返回值无法接收
- 作为形参列表,告知编译器or程序员不能传递参数
//无返回值
void func(int a)
{}//表明函数不用传入参数
int func2(void)
{return 0;
}
void是否可以定义变量
我们先来看看代码是不是会报错.最后再看原理.
int main()
{void a;return 0;
}
这个结果很明显,编译就过去,那我们就不得不疑惑了,为什么void不可以定义变量,首先我们要明白一件事,变量的存在需要给变量开辟空间用来存储数据.void是不能够开辟空间.
#include <stdio.h>int main()
{printf("%d\n", sizeof(void));return 0;
}
我们会发现,在VS2013上,void类型没有开辟空间,所以它是不能定义变量的.有的人可能会对Linux环境下感兴趣,我们也来看看吧.代码和上面的一样,这里我就给出结果了.我们可以轻易地发现,在Linux环境下,void开辟了一个空间,这是不是意味着在Linux环境下,void可以定义变量,很抱歉,这还是不可以的,即使Linux开辟了空间,但是编译器会认为它是空类型,禁止给它定义变量.
那么Linux为何什么环境下可以开辟空间,实际上gcc编译器不仅仅支持C语言标准,它还扩充乐意GNU计划,里面的内容大家有兴趣看看读读文档,这里不做要求.
void*
一般而言,这就是我们接触到所有的void的应用了,不过要是你模拟实现过C语言的qsort,你会发现另一个应用,void*,我们也遇见过使用maollc或者realloc开辟空间的时候最好给他们强制类型转换成我们想要的指针类型,那么这里我们就会很好奇,malloc函数的返回类型是什么?为何可以变成我们想要的任意指针类型.我们看看它的函数.
我们发现它的返回值是void* ,可是我们知道int*,float* ,那么请你告诉我void*究竟是神马玩意?
void* 也是指针,我们它可以接受任意类型的指针,也可以强制类型转换成任何类型的指针,由于我们都知道指针在32位平台下是4个字节,64位是8个字节.我们不知道malloc要开辟的空间指向什么类型,但是我们可以强制类型转换.
我们来看看void*在32位平台下占据多少个字节.
#include <stdio.h>
int main()
{printf("%d", sizeof(void*));return 0;
}
void*是不是可以解引用
解引用的作用是使得指针变成相对应的类型,我们就开始疑惑了,void*是不是也可以解引用.
int main()
{int a = 0;void* pa = &a;*pa;return 0;
}
我们可以发现,在Linux和Windows环境下,void*都不能解引用.这一点很重要的,我们想要解引用必须进行强制类型转换.
void*是不是可以加减整数
谈完了解引用,我们需要仔细的看看是不是加减帧整数,这里以加1来具体举例,要是它可以加减整数,加1跳过几个字节?和上面一样,都是在双环境下测试.
int main()
{int a = 0;void* pa = &a;void* pb = pa + 1;printf("%d", pb - pa);return 0;
}
在Windows环境,void都会不开辟空间,我们之前int* 加1跳过的是4个字节,那时=是因为int本身占据4个字节,void在Windows环境下是不会开辟空间的,它怎么跳?
在Linux环境下就可以很好理解了,void开辟了一个空间,所以void*+1会跳过一个字节.
因此void* 是不可以解引用或者是加减整数跳过几个字节关键在于void是不是可以开辟空间,这个和指针的内容对上了.
const
我想问一下,你在C语言中遇到过这个关键字吗,你真的了解它的原理吗?还是说你就用它来修饰一个变量?今天我将带你好好的看看它详细的用法.
const修饰变量
这个我们知道,不就是修饰一个变量使它变成常变量,那么请你告诉我,常变量是变量还是常量?你怎么验证?首先常变量是一个变量,只是拥有常量的属性,但是本质还是变量.,我们在C89标准下不支持变长数组,也就是说我们定义数组长度的时候必须是常量,要是常数变量是常量,那么编译器一定不报错.
int main()
{const int cap = 10;int arr[cap] = { 0 };return 0;
}
在Linux环境下我们出现了两种共情况,我们分别列出.
跑得过,可以编译和运行.原因是Linux 中除了C标准之外,还应用的GNU标准的C,这里被gcc编译器认为是变常数组了.
int main()
{const int cap = 10;int arr[cap];return 0;
}
出现报错 报错 : 可变大小的对象可能未初始化,这是由于gcc支持变长数组,变长数组规定 不能够初始化.
int main()
{const int cap = 10;int arr[cap] = {0};return 0;
}
从这里就可以看出,const修饰的变量就是一个变量,那么const不就是一个没有用到的东西吗?非也非也,我们是不是有这种情况,我们定义了了一个变量,不希望自己或者其他人对他进行修改,该怎办?const就可以解决这个问题.
int main()
{const int cap = 10;cap = 20;return 0;
}
const修饰变量的原理
我么就很疑惑,难道const修饰的变量就真的没有办法更改吗?要是不能修改,和常量又有什么区别!!!,所以说它是一定可以修改的,下面就是一种修改方法.我不直接改变它的值,我找到他所在的空间,我把它空间的里面的只给改了,这样就可以间接修改了const修饰的的变量.
int main()
{const int cap = 10;int* p = ∩*p = 20;printf("%d\n", cap);return 0;
}
到这里我们就要考虑const的原理了,用const修饰变量就像我们把一袋金子放到屋子里面,我们把屋子的门给锁上,这样就不害怕有小偷来偷走它了.但是现在的小偷很聪明,既然我从门进不去,但是我看到窗户没有上锁,我从这爬进去,虽然方式不同,但是我还是拿到了金子.const就相当于那把门锁.
可是这里给大家提出一个问题,为何这里我们不能进行偷家
const int flag = 1;int main()
{int* const p = &flag;while (flag){printf("%d \n", flag);*p = 0;}printf("-------------");return 0;
}
那么我想的问的是const存在有什么意思呢?下面就是.
1、让编译器进行修改时的检查
2、让其他程序员看到,提醒他不要不要修改这个值
真正的常量
我们刚才谈了变量,这里给大家看看什么是真正的常量.像1,2,3…这些都是常量,这里还有一个字符串常量.const 的不能修改是指对编译器而言的,而“abcdef”是在字符串常量区,是系统不让修改的.
int main()
{char* p = "hello";//常量字符串*p = 'H';printf("%c", p);return 0;
}
我们去gcc中测试一下,直接段溢出.
const与指针
上面的都太简单了,这里我们需要看看const的进阶部分.在这里之前,我们知道下面两种修饰是一模一样的,那么
const int cap = 10;
int const cap = 10;
那么我们是不是可以通过const来说修饰指针.看看他们会有什么区别吧.你来看看下面的代码有什么区别吗?
int main()
{int a = 0;const int* pa = &a;int const *pb = &a;int* const pc = &a;const int* const pd = &a;return 0;
}
这是什么鬼?不是为难我胖虎吗?大家先不要着急,我们一个一个来分析.
const int* pa = &a 和 int const *pb = &a
他们都是const离* 最近,所以const int* pa = &a 和 int const *pb = &a 中const修饰的是 * ,也就是说 pa 和 pb不能够进行解引用.
int main()
{int a = 0;const int* pa = &a;*pa = 10;return 0;
}
但是p可以指向另外的地址
int main()
{int a = 0;int b = 20;const int* pa = &a;pa = &b;return 0;
}
int* const pc = &a;
const里pc最近,所以const修饰的是 pc,也就是说pc可以解引用,对所指的空间再次赋值,但是不能再次指向其他空间
int main()
{int a = 0;int b = 20;int* const pc = &a;*pc = 20;pc = &b;return 0;
}
const int * const pd = &a;
两个都被const修饰了,所以既不能解引用又不能再次指向.
int main()
{int a = 0;int b = 20;const int * const pd = &a;*pd = 20;pd = &b;return 0;
}
goto
很多公司确实禁止使用goto,不过,这个问题我们还是灵活对待,goto在解决很多问题是有奇效的。我们可以认为goto使用场景较少,一般不使用。但是必须得知道goto,需要的时候,也必须会用.这个我们可以认为是是程序执行的时候直接跳跃.
int main()
{goto end;printf("hello word\n");
end:printf("hello end\n");return 0;
}
还有下面的一种情况,这里还可以跳到上面.
int main()
{int a = 10;
end:{printf("hello end\n");a = 0;}if (a){goto end;printf("hello word\n");}return 0;
}
goto语句主要用来跳出多层循环语句,由于循环过深,因为用break要用很多次
for(...)for(...){for(...){if(disaster)goto error;}}…
error:if(disaster)// 处理错误情况
写在前面
之前我零零散散的写了谈了一些关于C语言关键字的内容,今天想和大家集中分享一下。这些都是我看一些是视频解说和一些书籍总结出来的,里面的内容深度也比较高,但是比较简单。一些内容是我们有时没有注意到的,我会尽量涉及到。由于能力有限,有什么错误疏漏的地方还请多多担待。
void关键字
我们先来谈谈在你平常遇到void的方式都有哪几种?一般情况而言,有下面连种方式。
- 告知编译器这个返回值无法接收
- 作为形参列表,告知编译器or程序员不能传递参数
//无返回值
void func(int a)
{}//表明函数不用传入参数
int func2(void)
{return 0;
}
void是否可以定义变量
我们先来看看代码是不是会报错.最后再看原理.
int main()
{void a;return 0;
}
这个结果很明显,编译就过去,那我们就不得不疑惑了,为什么void不可以定义变量,首先我们要明白一件事,变量的存在需要给变量开辟空间用来存储数据.void不能够开辟空间.
#include <stdio.h>int main()
{printf("%d\n", sizeof(void));return 0;
}
我们会发现,在VS2013上,void类型没有开辟空间,所以它是不能定义变量的.有的人可能会对Linux环境下感兴趣,我们也来看看吧.代码和上面的一样,这里我就给出结果了.
我们可以轻易地发现,在Linux环境下,void开辟了一个空间,这是不是意味着在Linux环境下,void可以定义变量,很抱歉,这还是不可以的,即使Linux开辟了空间,但是编译器会认为它是空类型,禁止给它定义变量.
那么Linux为何什么环境下可以开辟空间,实际上gcc编译器不仅仅支持C语言标准,它还扩充乐意GNU计划,里面的内容大家有兴趣看看读读文档.
void*
一般而言,这就是我们接触到所有的void的应用了,不过要是你模拟实现过C语言的qsort,你会发现另一个应用,void*,我们也遇见过使用maollc或者realloc开辟空间的时候最好给他们强制类型转换成我们想要的指针类型,那么这里我们就会很好奇,malloc函数的返回类型是什么?为何可以变成我们想要的任意指针类型.我们看看它的函数.
我们发现它的返回值是void* ,可是我们知道int*,float* ,那么请你告诉我void*究竟是神马玩意?
void* 也是指针,我们它可以接受任意类型的指针,也可以强制类型转换成任何类型的指针,由于我们都知道指针在32位平台下是4个字节,64位是8个字节.我们不知道malloc要开辟的空间指向什么类型,但是我们可以强制类型转换.
我们来看看void*在32位平台下占据多少个字节.
#include <stdio.h>
int main()
{printf("%d", sizeof(void*));return 0;
}
void*是不是可以解引用
解引用的作用是使得指针变成相对应的类型,我们就开始疑惑了,void*是不是也可以解引用.
int main()
{//printf("%d\n", sizeof(void));int a = 0;void* pa = &a;*pa;return 0;
}
我们可以发现,在Linux和Windows环境下,void*都不能解引用.这一点很重要的.
void*是不是可以加减整数
谈完了解引用,我们需要仔细的看看是不是加减帧整数,这里以加1来具体举例,要是它可以加减整数,加1跳过几个字节?和上面一样,都是在双环境下测试.
int main()
{int a = 0;void* pa = &a;void* pb = pa + 1;printf("%d", pb - pa);return 0;
}
在Windows环境,void都会不开辟空间,我们之前int* 加1跳过的是4个字节,那时=是因为int本身占据4个字节,void在Windows环境下是不会开辟空间的,它怎么跳?
在Linux环境下就可以很好理解了,void开辟了一个空间,所以void*+1会跳过一个字节.
const关键字
我想问一下,你在C语言中遇到过这个关键字字吗,你真的了解它的原理吗?还是说你就用它来修饰一个变量?今天我将带你好好的看看它详细的用法.
const修饰变量
这个我们知道,不就是修饰一个变量使它变成常变量,那么请你告诉我,常变量是变量还是常量?你怎么验证?
首先常变量是一个变量,只是拥有常量的属性,但是本质还是变量.,我们在C89标准下不支持变长数组,也就是说我们定义数组长度的时候必须是常量,要是常数变量是常量,那么编译器一定不报错.
int main()
{const int cap = 10;int arr[cap] = { 0 };return 0;
}
在Linux环境下我们出现了两种共情况,我们分别列出.
跑得过,可以编译和运行.原因是Linux 中除了C标准之外,还应用的GNU标准的C
int main()
{const int cap = 10;int arr[cap];return 0;
}
出现报错 报错 : 可变大小的对象可能未初始化,这是由于gcc支持变长数组,变长数组规定 不能够初始化为.
int main()
{const int cap = 10;int arr[cap] = {0};return 0;
}
从这里就可以看出,const修饰的变量就是一个变量,那么const不就是一个没有用到的东西吗?非也非也,我们是不是有这种情况,我们定义了了一个变量,不希望自己或者其他人对他进行修改,该怎办?const就可以解决这个问题.
int main()
{const int cap = 10;cap = 20;//int arr[cap] = { 0 };return 0;
}
const修饰变量的原理
我么就很疑惑,难道const修饰的变量就真的没有办法更改吗?要是不能修改,和常量又有什么区别!!!,所以说它是一定可以修改的,下面就是一种修改方法.我不直接改变它的值,我找到他所在的空间,我把它空间的里面的只给改了,这样就可以间接修改了const修饰的的变量.
int main()
{const int cap = 10;int* p = ∩*p = 20;printf("%d\n", cap);return 0;
}
到这里我们就要考虑const的原理了,用const修饰变量就像我们把一袋金子放到屋子里面,我们把屋子的门给锁上,这样就不害怕有小偷来偷走它了.但是现在的小偷很聪明,既然我从门进不去,但是我看到窗户没有上锁,我从这爬进去,虽然方式不同,但是我还是拿到了金子.const就相当于那把门锁.
const存在的意义
1、让编译器进行修改时的检查
2、让其他程序员看到,提醒他不要不要修改这个值
真正的常量
我们刚才谈了变量,这里给大家看看什么是真正的常量.像1,2,3…这些都是常量,这里还有一个字符串常量.
const 的不能修改是指对编译器而言的,而“abcdef”是在字符串常量区,是系统不让修改的
int main()
{char* p = "hello";//常量字符串*p = 'H';printf("%c", p);return 0;
}
const与指针
上面的都太简单了,这里我们需要看看const的进阶部分.在这里之前,我们知道下面两种修饰是一模一样的,那么
const int cap = 10;
int const cap = 10;
那么我们是不是可以通过const来说修饰指针.看看他们会有什么区别吧.你来看看下面的代码有什么区别吗?
int main()
{int a = 0;const int* pa = &a;int const *pb = &a;int* const pc = &a;const int* const pd = &a;return 0;
}
这是什么鬼?不是为难我胖虎吗?大家先不要着急,我们一个一个来分析.
const int* pa = &a 和 int const *pb = &a
他们都是const离 最近,所以const int pa = &a 和 int const *pb = &a 中const修饰的是 * ,也就是说 pa 和 pb不能够进行解引用.
int main()
{int a = 0;const int* pa = &a;*pa = 10;return 0;
}
但是p可以指向另外的地址
int main()
{int a = 0;int b = 20;const int* pa = &a;pa = &b;return 0;
}
int* const pc = &a;
const里pc最近,所以const修饰的是 pc,也就是说pc可以解引用,对所指的空间再次赋值,但是不能再次指向其他空间
int main()
{int a = 0;int b = 20;int* const pc = &a;*pc = 20;pc = &b;return 0;
}
const int * const pd = &a;
两个都被const修饰了,所以既不能解引用又不能再次指向.
int main()
{int a = 0;int b = 20;const int * const pd = &a;*pd = 20;pd = &b;return 0;
}
总结:
const与谁靠的近,就修饰谁,谁就不可以再次改变
static关键字
关于staic关键字,我有很多想和大家分享的,它是在太让我们忽略了,即使是现在我还需要借助我以前的笔记来写这篇博客.我脑子就记住了一个,static修饰局部变量改变它的生命周期,不改变作用域.下面是我总结的一些static的作用.
- 修饰局部变量
- 修饰全局变量
- 修饰函数
修饰局部变量
static修饰局部变量改变它的生命周期,不改变作用域,我们先来看看代码和现象.
void fun()
{int a = 1;//不用 static修饰a++;printf("%d ",a);
}int main()
{int i = 0;while (i < 10){fun();i++;}printf("\n");return 0;
}
void fun()
{static int a = 1; // static修饰,在程序运行前只进行一次初始化a++;printf("%d ",a);
}int main()
{int i = 0;while (i < 10){fun();i++;}printf("\n");return 0;
}
从现象我们可以看出,static 修饰的变量的 空间没有被销毁,否则不会打印出3 4 5… 我们证明一下,看看打印出来a的值是
可以看出,a的值是确定值,其地址没有被销毁,那么我们可以得到static改变的局部变量的生命周期
那么作用域变了没有? ------- 作用域没有改变.
为什么static可以改变局部变量的生命周期?
static修饰的局部变量,会在全局数据区或者静态数据区开辟空间(编译器的不同),这就造成了static可以改变局部变量的生命周期。
详细的可以看一下C程序地址空间
为什么函数和全局变量可以跨文件访问
在谈这个之前,我们需要说一说多文件,为何我们要定义几个文件,我们可以试想一下这样的场景,我们写的函数很多,当我们使用函数的时候发现要找好久,有时还不知道函数的参数和返回值,我们是不是可以定义一个头文件,把自己的写的函数都声明出来.
- .h:我们称之为头文件,一般包含函数声明,变量声明,宏定义,头文件等内容(header)
- .c: 我们称之为源文件,一般包含函数实现,变量定义等
我们写的大型项目一般都是多文件项目,文件与文件之间一定要可以进行跨文件访问,否则,我们不能跨文件,那么“交互”的成本就比较高。但总有些代码需要隐藏,所以出现了static这个关键字。
修饰全局变量
static修饰的全局变量只能在本文件中内被访问,不能被外部文件直接访问.
未用static修饰
用static修饰
全局变量拥有外部链接属性,被static修饰后,外部链接属性好像消失了
修饰函数
先不来谈这个,我们先看卡这种情况,我们在test.c里面定义一个函数,在main.c里面直接调用,什么都不做,这个代码会不会报错.
//test.c
void show()
{printf("你可调用到我\n");
}//main.c#include <stdio.h>int main()
{show();return 0;
}
我在mian.c里面都没有声明这个函数,为毛还会出现正确的关键字?这是怎么回事.这是由于函数具有外部链接属性,当我们连接时,编译器会自动去寻找这个函数.
static修饰的函数和全局变量一样,只能在本文件中内被访问,不能被外部文件直接访问
未用static修饰
用static修饰
那被static修饰的函数如何可以间接访问
在static修饰的函数的文件内,可以再写一个函数调用被static修饰的函数,在外部文件调用该函数,就可以间接调用static修饰的函数了
总结
- static 修饰局部变量,改变的是生命周期,不改变作用域。
- static修饰函数,目的在于封装,提高代码的安全性。使用户只能使用该文件,但是不能随意修改里面的代码,static提供项目维护、安全保护。
相关文章:
C语言常见关键字
写在前面 这个博客是结合C语言深度解剖这本书和我以前学的知识综合而成的,我希望可以更见详细的谈一下C语言的关键字,内容有点多,有错误还请斧正. 常见关键字 下面我们说下C语言的关键字,所谓的关键字是指具有特定功能的单词,我们可以使用关键字来帮助我们完成不同的事物.C语…...
【MT7628】固件开发-SDK4320添加MT7612E WiFi驱动操作说明
解压5G WiFi MT7612E驱动1.1解压指令 tar -xvf MT76x2E_MT7620_LinuxAP_V3.0.4.0_P2_DPA_20160308.tar.bz2 1.2解压之后会出现以下两个目录 rlt_wifi rlt_wifi_ap 1.3将解压后的文件拷贝到系统下 拷贝路径 RT288x_SDK/source/linux-2.6.36.x/drivers/net/wireless 内核中打开驱…...
如何从手工测试进阶自动化测试?阿里10年测开经验分享...
随着行业的竞争加剧,互联网产品迭代速度越来越快,QA 与测试工程师都需要在越来越短的测试周期内充分保证质量。可是,App 测试面临着很多挑战,比如多端发布、多版本发布、多机型发布等等,导致了手工测试很难完全胜任。因…...
C++复习笔记11
1. vector是表示可变大小数组的序列容器。 2. 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被…...
【MT7628】固件开发-SDK4320添加MT7628 WiFi驱动操作说明
解压2.4G WiFi MT7628驱动1.1解压指令 tar -xvf MT7628_LinuxAP_V4.1.0.0_DPA_20160310.tar.bz2 1.2解压之后会出现以下两个目录 mt_wifi mt_wifi_ap 1.3将解压后的文件拷贝到系统下 拷贝路径 RT288x_SDK/source/linux-2.6.36.x/drivers/net/wireless 内核中打开驱动编译修改R…...
C#开发的OpenRA游戏加载界面的实现
C#开发的OpenRA游戏加载界面的实现 游戏的UI是一个游戏必备, 但是游戏的UI都是自己处理的,不能使用像Windows自带的UI。 这样游戏的UI,其实也是使用游戏的方式来显示的, 只不过使用了低帧率的方式来显示。 比如OpenRA游戏界面,就会显示如下: 游戏的界面有很多,先从一个简…...
渲染农场优势是什么_云渲染农场怎么用?
在回答渲染农场的优势这个问题之前,我先申明一下本文中提到的渲染农场/云渲染平台/云渲染农场,都特指CG领域内的专业3D渲染平台,有一些文章会强调这个叫法的区别,但是业内一般都不会分这么细,所以也就不赘述了。渲染农…...
SoapUI、Jmeter、Postman三种接口测试工具的比较分析
目录 前言 1. 用例组织方式 2. 支持的接口类型与测试类型 3. 配置不同接口类型 4. 自定义变量以及变量的作用域 5. 数据源、生成器,进行参数化 6. 流程控制 7. 结果解析、展示 8. 断言 9. 脚本扩展能力 10. 团队协作 总结 重点:配…...
Python内置函数 — sort,sorted
1、sort 列表的属性方法,对列表进行排序,默认升序,返回None值。 源码注释: """ Sort the list in ascending order and return None.The sort is in-place (i.e. the list itself is modified) and stable (i.e.…...
mysql事务隔离级别
mysql锁机制及原理1.隔离级别2.实践2.1查看事务隔离级别2.2 设置隔离级别2.3 不可重复读2.4 幻读3.幻读怎么解决3.1 Record Lock3.2 Gap Lock3.3 Next-Key Lock引用:https://blog.csdn.net/xinyuan_java/article/details/1284932051.隔离级别 SERIALIZABLE(序列化)…...
【C++】string类(下)
文章目录1.迭代器(正向遍历)begin有两个版本2.反向迭代器(反向遍历)rbegin由两个版本3. at4. insert ——头插在pos位置前插入一个字符串在pos位置前插入n个字符在迭代器前插入一个字符5. erase从pos位置开始删除len个字符从迭代器位置开始删除6. replace——替换从pos位置开始…...
Elasticsearch: Prefix queries - 前缀查询
Prefix queries 被用于在查询时返回在提供的字段中包含特定前缀的文档。有时我们可能想使用前缀查询单词,例如 Leonardo 的 Leo 或 Marlon Brando、Mark Hamill 或 Martin Balsam 的 Mar。 Elasticsearch 提供了一个前缀查询,用于获取匹配单词开头部分&a…...
GEE学习笔记 七十七:GEE学习方法简介
这是一篇关于学习方法的思考探索,当然我不会大篇文章介绍什么学习方法(因为我也不是这方面的专家?),这个只是总结一下我是如何学习GEE以及在学习中遇到问题时如何解决问题的。我写这篇文章的目的就是在和一些学习GEE的新同学接触…...
20基于主从博弈的智能小区代理商定价策略及电动汽车充电管理MATLAB程序
参考文档:《基于主从博弈的智能小区代理商定价策略及电动汽车充电管理》基本复现仿真平台:MATLABCPLEX/gurobi平台优势:代码具有一定的深度和创新性,注释清晰,非烂大街的代码,非常精品!主要内容…...
长按power键,点击重启按钮,系统重启流程一
1.有可能会涉及到如下文件 2.文件流程...
数据的TCP分段和IP分片
本文简述下TCP分段和IP分片的区别与联系。 我们知道,用户空间的数据拷贝到内核空间的TCP发送缓冲区(这个是一个结构体,叫sk_buffer,简称skb)后就由内核网络协议栈做后续的封装和发送处理了,用户无需考虑下…...
HTML中嵌入B站视频
HTML中嵌入B站视频 在网页中实现一个HTML播放器需要先从b站获取视频嵌入代码, 以前嵌入代码可以从视频分享那里拿到, 现在好像不行了 必须是自己投稿的视频, 从投稿管理页面才能找到 复制嵌入代码 建一个.html文件, 放入下面代码 <!DOCTYPE html> <html><head…...
Mars3D Studio 的使用方法
Mars3D Studio的使用 1、介绍: mars3d Studio是mars3d研发团队于近期研发上线的一款 场景可视化编辑平台。拥有资源存档、团队协作、定制材质等丰富的功能。可以实现零代码构建一个可视化三维场景。 2、功能介绍 (1)数据上传:…...
Flutter For Web实践
1 什么是Flutter Flutter是Google开源的一套UI工具包,帮助开发者通过一套代码库高效构建多平台精美应用,支持移动APP、web、桌面和嵌入式平台。Flutter和其他的跨平台解决方案的实现方式上有比较大的差异。 我们以React Native(下文简称RN&…...
【神级Python代码】作为技术xiao白如何制作一款超炫酷的黑客主题代码雨?牛逼就完了。(源码分享学习)
前言 哈喽,我是木子,今天给大家制作一款超级炫酷的代码啦。 提到《黑K帝国》,字符雨可谓是让人印象深刻。 所有文章完整的素材源码都在👇👇 粉丝白嫖源码福利,请移步至CSDN社区或文末公众hao即可免费。 …...
供应链挑战迎刃而解!桑迪亚国家实验室使出“量子杀手锏”
桑迪亚国家实验室的科学家Alicia Magann(右),Kenneth Rudinger(左上),Mohan Sarovar(左下)和Matthew Grace(未附图)开发了基于反馈的量子优化算法(…...
java程序设计-ssm博客管理系统
博客管理系统是一个用于创建、管理和发布博客文章的应用程序。它通常包括一个后台管理界面,用于管理用户、文章、评论、标签等数据。同时,它还包括一个前端界面,用于展示博客文章并提供交互功能,例如评论和分享。 博客管理系统可…...
从0到1一步一步玩转openEuler--17 openEuler DNF(YUM)检查更新
文章目录17.1 检查更新17.2 升级17.3 更新所有的包和它们的依赖DNF是一款Linux软件包管理工具,用于管理RPM软件包。DNF可以查询软件包信息,从指定软件库获取软件包,自动处理依赖关系以安装或卸载软件包,以及更新系统到最新可用版本…...
SpringBoot-自动配置-@Import注解与@EnableAutoConfiguration注解
Import注解 Enable* 底层依赖于 Import 注解导入一些类,使用 Import 导入的类会被 Spring 加载到 IOC 容器中Import 提供了4种用法: 1.导入Bean2.导入配置类3.导入ImportSelector实现类;一般用于加载配置文件中的类4.导入ImportBeanDefinitio…...
【笔记】C#一维数组、多维数组和交错数组的区别总结
文章目录前言数组的概念1,一维数组:2,多维数组:3,交错数组:区别总结结语前言 😄大家好,我是writer桑, 这是自己整理的 C# 数组笔记,方便自己学习的同时分享出…...
【SpringBoot】分布式日志跟踪—通过MDC实现全链路调用日志跟踪
一.MDC 1.MDC介绍 MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 提供的一种方便在多线程场景下记录日志的功能。MDC 可以看成是一个与当前线程绑定的Map,可以往其中添加键值对。MDC 中包含的内容可以被同…...
【设计模式】创建型模式
简单工厂模式 系列综述: xxxxxxxxx 文章目录对象创建型模式简单(静态)工厂模式工厂方法模式参考博客😊点此到文末惊喜↩︎ 对象创建型模式 简单(静态)工厂模式 抽象原理 抽象产品基类 :定义了…...
Spark Catalyst 查询优化器原理
这里我们讲解一下SparkSQL的优化器系统Catalyst,Catalyst本质就是一个SQL查询的优化器,而且和 大多数当前的大数据SQL处理引擎设计基本相同(Impala、Presto、Hive(Calcite)等)。了解Catalyst的SQL优化流程&…...
贝叶斯分析法在市场调研中的应用
一、市场调研的需求场景 在营销活动的用研调研时,我们经常会去问用户在不同平台的品类付费情况,以对比大促期间本品和竞品分别在哪些品类上具有市场优势,他们之间的差距具体在哪里、差距有多大。假如根据调研问卷结果,我们知道拼多多用户有30%的人在大促购买生鲜类,而淘宝…...
JavaEE——MyBatis将查询结果集封装进POJO实体类
简单介绍 在之前的我们比较详细的介绍过MyBatis的配置信息的时候,在SQL映射文件中说过我们可以直接将结果集映射到我们的POJO实体类中,省去了我们自己处理查询结果集的时间和代码,接下来我们就来演示将单条数据和多条数据映射到我们POJO实体…...
普洱专业企业网站建设/开封网站快速排名优化
问题描述 LIS(Longest Increasing Subsequence,最长递增子序列):给出一个序 列a1,a2,a3,a4,a5,a6,a7…an,求它的一个子序列(设为s1,s2,…sn),使得这个 子序列满足这样的性质,s1 最长递增子序列 实例分析 1 7 3 5 9 …...
新开传奇网站999新服网/网站搭建策略与方法
Proactor IO即,在读写IO完成后的通知。 Reactor模式,采用的非阻塞模式,但资源的读写操作仍然是同步的。 而Proactor模式在读写操作上是异步完成的,资源读写靠操作系统完成,而不是应用进程。操作系统完成读写后&#x…...
做网站百度百科/搜索引擎优化的流程是什么
企业渗透测试流程有哪些?企业渗透测试是指对企业网络、应用程序、系统、设备等进行安全测试,以发现潜在的安全漏洞和威胁,从而提高企业的安全性和保护企业的数据。下面将介绍企业渗透测试的流程。 企业渗透测试流程有哪些? 1. 确…...
wordpress添加html代码/外包推广服务
一.HTTP请求/响应 的抓包结果分析 以下是点击浏览器程序的请求 1.首行: [方法] [url] [版本] 2.Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部 分结束 3.空行 3.Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在…...
阿里云 wordpress cdn/seo的工作内容主要包括
今天为了将mxnet框架转为tensorflow 框架,参考了下面帖子来操作一下 pip install mmdnn 装好后,页面有提示,就先忽略掉了 mxnet-cu100 1.4.1 has requirement numpy<1.15.0,>1.8.2, but youll have numpy 1.16.4 which is incompatible. mxnet-cu90 1.4.1 has require…...
建站宝盒可以做视频聊天交友网站吗/品牌运营具体做什么
准备材料AndroidStudio谷歌android-serialport-api前情提要网上提供很多基于c语言对安卓串口开发,有jni、cmake等等,不过都太高深,谷歌提供的api已经可以满足基本读写(对数据位、停止位、校验位无要求,默认N81),这也是…...