写在前面
这个博客是结合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 100
int 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提供项目维护、安全保护。