C语言常见关键字

news2024/11/18 11:45:52

写在前面

这个博客是结合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;
}

image-20230122183529555

注意,我们这里起外号之后,使用的方法和原本的是没有任何区别的,这在内置类型或许还不太明显,主要是内置类型的用法是在是太简单了,再后面的额函数指针和自定义类型就非常厉害了,这我们后面再说.

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;
}

image-20230123091115763

这是由于*我们可以认为之和p结合,这里有涉及到代码规范的问题.如果我们使用宏,会出现下面的情况,但是typedef不会.

#include <stdio.h>
typedef int* Ptr;
#define INT_P int*

int main()
{
	INT_P p1, a;
	Ptr p2, b;
	return 0;
}

image-20230123091353940

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;
}

image-20230122185138929

那么我想问的是上面我们说的是auto是局部变量,那么请问我们是不是可以用auto来修饰全局变量?注意这是不行的

image-20230122185548952

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;
}

image-20230122190848220

下面我们补充点东西,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;
}

image-20230122191046547

extern

这里我们需要引入一些东西,我们已经知道变量的存在了,但是有的问题还是需要我们进行讨论的.

什么是变量

变量是程序在运行时在内存中开辟特定大小的空间,用来保存数据。

我们这里抓取两个关键字,一个是运行,一个是内存.先来解释运行.

所谓饿饿运行就是程序跑起来的时候,只有这时候我们的空间才会被开辟,如没有运行那么所谓的变量就是一个个字符,只有书面意思,没有实际的意思.

那么变量为何要在内存开辟呢?他不会去硬盘中开辟吗?因为变量是程序在运行的时候开辟的,程序在运行后已经被加载到内存。也就是说,当我们要定义变量的时候,程序已经在内存里了,所以变量只能在内存中开辟空间。

换言之,虽然我们已经在编译器中定义了一个变量,但是这个变量并不会在我们写下这句代码的时候就开辟内存,而是必须要整个程序都加载到内存之后才会在内存中开辟空间。

为何要有变量

计算机的诞生是为了解决人计算能力不足的问题而诞生的。即,计算机是为了进行计算的。 而计算,就需要数据。 而要计算,任何一个时刻,不是所有的数据都要立马被计算。 如同:要吃饭,不是所有的饭菜都要立马被你吃掉。饭要一口一口吃,那么你还没有吃到的饭菜,就需要暂时放在盘子里。 这里的盘子,就如同变量,饭菜如同变量里面的数据。 换句话说,为何需要变量?因为有数据需要暂时被保存起来,等待后续处理。 那么,为什么吃饭要盘子?我想吃一口菜了,直接去锅里找不行吗?当然行,但是效率低。 因为我们吃饭的地方,和做饭的地方,是比较"远"的。

变量定义

类型 变量名 = 默认值

int main()
{
	int a = 10;
	return 0;
}

我们把在变量定义出来的那一刻赋值称之初始化,前面我们说了变量出来我们最好要初始化,这里说一下对数组的不完全初始化.如果我们数组没有初始化完整,后面会自动补成0.

int main()
{
	int arr[10] = { 0 };
	return 0;
}

image-20230123093550451

变量的声明

不知道大家对于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;
}

image-20220429190630246

这是由于VS再编译程序中会把所有的源文件编译成.o文件,这里涉及到编译原理的知识,我们先不谈,记住这样的现象就可以了.

image-20220429185250737

这里有一个问题,要是我们再次在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;
}

image-20220429190252067

我们发现运行时过不了,编译器在链接时寻找变量,它不能够区分这两g_val有什么不同.那么我们在两个文件中声明全局变量时给它赋值会怎么样?

//main.c
extern int g_val = 20;

int main()
{
	printf("%d", g_val);
	return 0;
}

image-20220429190557202

这个是不可能过的,extern的作用是声明变量,它不会开辟空间,我们初始化或者是赋值都是把数据放到开辟的内存当中,一定会报错.可是我们有疑惑了,按照道理而言,下面是存在一个文件中的,下面的代码应该是错误的,为什么会跑过?

#include <stdio.h>
extern int g_val = 10;

int main()
{
	printf("%d", g_val);
	return 0;
}

image-20220429191129777

声明函数.所谓的声明函数也是一样的,这里我们就不谈了,主要是现在很少用

定义与声明的本质

定义变量的本质,程序在运行时为其变量或函数开辟内存空间,用来保存数据。至于初始化,则是完成内存空间开辟,为其开辟的内存填指定的值。变量只能定义一次,但是一个变量可以声明多次,声明只是告诉了编译器存在这么一个变量或者函数,这个函数或者变量在其他的位置定义过了,所以在这个过程中没有为其再次分配内存.他们最大的区别就是有无分配内存。

static

关于staic关键字,我有很多想和大家分享的,它是在太让我们忽略了,即使是现在我还需要借助我以前的笔记来写这篇博客.我脑子就记住了一个,static修饰局部变量改变它的生命周期,不改变作用域.下面是我总结的一些static的作用.

  1. 修饰局部变量
  2. 修饰全局变量
  3. 修饰函数

修饰局部变量

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;
}
img

下面就是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;
}
img

从现象我们可以看出,static 修饰的变量的 空间没有被销毁,否则不会打印出3 4 5… 我们证明一下,看看打印出来a的值是

img

可以看出,a的值是确定值,其地址没有被销毁,那么我们可以得到static改变的局部变量的生命周期,它是在整个程序中都有效的.那么作用域变了没有? ------- 作用域没有改变.

img

为什么static可以改变局部变量的生命周期?static修饰的局部变量,会在全局数据区或者静态数据区开辟空间(编译器的不同),这就造成了static可以改变局部变量的生命周期。详细的可以看一下C程序地址空间

img

为什么函数和全局变量可以跨文件访问

在谈这个之前,我们需要说一说多文件,为何我们要定义几个文件,我们可以试想一下这样的场景,我们写的函数很多,当我们使用函数的时候发现要找好久,有时还不知道函数的参数和返回值,我们是不是可以定义一个头文件,把自己的写的函数都声明出来.

  • .h:我们称之为头文件,一般包含函数声明,变量声明,宏定义,头文件等内容(header)
  • .c: 我们称之为源文件,一般包含函数实现,变量定义等

我们写的大型项目一般都是多文件项目,文件与文件之间一定要可以进行跨文件访问,否则,我们不能跨文件,那么“交互”的成本就比较高。但总有些代码需要隐藏,所以出现了static这个关键字。

修饰全局变量

static修饰的全局变量只能在本文件中内被访问,不能被外部文件直接访问.

img

全局变量拥有外部链接属性,被static修饰后,外部链接属性好像消失了

img

修饰函数

先不来谈这个,我们先看下这种情况,我们在test.c里面定义一个函数,在main.c里面直接调用,什么都不做,这个代码会不会报错.

//test.c
void show()
{
	printf("你可调用到我\n");
}

//main.c

#include <stdio.h>

int main()
{
	show();
	return 0;
}

image-20220429115108265

image-20220429115325825

我在mian.c里面都没有声明这个函数,为毛还会出现正确的关键字?这是怎么回事.这是由于函数具有外部链接属性,当我们连接时,编译器会自动去寻找这个函数

static修饰的函数和全局变量一样,只能在本文件中内被访问,不能被外部文件直接访问

img

可是当我们使用static修饰之后我们就发现无法调用了,这里主要是为了封装一些只在本文件使用的接口.

img

那被static修饰的函数如何可以间接访问在static修饰的函数的文件内,可以再写一个函数调用被static修饰的函数,在外部文件调用该函数,就可以间接调用static修饰的函数了

img

下面我给一个总结.

  1. static 修饰局部变量,改变的是生命周期,不改变作用域。
  2. static修饰函数,目的在于封装,提高代码的安全性。使用户只能使用该文件,但是不能随意修改里面的代码,static提供项目维护、安全保护。

register

我们需要了解一些计算机组成的知识,根据机械原理,较大的存储设备要比较小存储设备运行的慢,而快速设备的造价要高得多,所以系统设计者采用高速缓存存储器作为集结区域,用于处理器存放近期可能用到的信息,存储器结构层次已经深入人心。

img

距离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;
}

image-20220428220820054

这个结果很明显,编译就过去,那我们就不得不疑惑了,为什么void不可以定义变量,首先我们要明白一件事,变量的存在需要给变量开辟空间用来存储数据.void是不能够开辟空间.

#include <stdio.h>

int main()
{
	printf("%d\n", sizeof(void));
	return 0;
}

image-20220429082411416

我们会发现,在VS2013上,void类型没有开辟空间,所以它是不能定义变量的.有的人可能会对Linux环境下感兴趣,我们也来看看吧.代码和上面的一样,这里我就给出结果了.我们可以轻易地发现,在Linux环境下,void开辟了一个空间,这是不是意味着在Linux环境下,void可以定义变量,很抱歉,这还是不可以的,即使Linux开辟了空间,但是编译器会认为它是空类型,禁止给它定义变量.

image-20220429082923485

那么Linux为何什么环境下可以开辟空间,实际上gcc编译器不仅仅支持C语言标准,它还扩充乐意GNU计划,里面的内容大家有兴趣看看读读文档,这里不做要求.

void*

一般而言,这就是我们接触到所有的void的应用了,不过要是你模拟实现过C语言的qsort,你会发现另一个应用,void*,我们也遇见过使用maollc或者realloc开辟空间的时候最好给他们强制类型转换成我们想要的指针类型,那么这里我们就会很好奇,malloc函数的返回类型是什么?为何可以变成我们想要的任意指针类型.我们看看它的函数.

image-20220428203457954

我们发现它的返回值是void* ,可是我们知道int*,float* ,那么请你告诉我void*究竟是神马玩意?

void* 也是指针,我们它可以接受任意类型的指针,也可以强制类型转换成任何类型的指针,由于我们都知道指针在32位平台下是4个字节,64位是8个字节.我们不知道malloc要开辟的空间指向什么类型,但是我们可以强制类型转换.

我们来看看void*在32位平台下占据多少个字节.

#include <stdio.h>
int main()
{
	printf("%d", sizeof(void*));
	return 0;
}

image-20220428220349071

void*是不是可以解引用

解引用的作用是使得指针变成相对应的类型,我们就开始疑惑了,void*是不是也可以解引用.

int main()
{
	int a = 0;
	void* pa = &a;
	*pa;
	return 0;
}

我们可以发现,在Linux和Windows环境下,void*都不能解引用.这一点很重要的,我们想要解引用必须进行强制类型转换.

image-20220429085602748

image-20220429085826059

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环境下是不会开辟空间的,它怎么跳?

image-20220429090440831

在Linux环境下就可以很好理解了,void开辟了一个空间,所以void*+1会跳过一个字节.

image-20220429091244021

因此void* 是不可以解引用或者是加减整数跳过几个字节关键在于void是不是可以开辟空间,这个和指针的内容对上了.

const

我想问一下,你在C语言中遇到过这个关键字吗,你真的了解它的原理吗?还是说你就用它来修饰一个变量?今天我将带你好好的看看它详细的用法.

const修饰变量

这个我们知道,不就是修饰一个变量使它变成常变量,那么请你告诉我,常变量是变量还是常量?你怎么验证?首先常变量是一个变量,只是拥有常量的属性,但是本质还是变量.,我们在C89标准下不支持变长数组,也就是说我们定义数组长度的时候必须是常量,要是常数变量是常量,那么编译器一定不报错.

int main()
{
	const int cap = 10;
	int arr[cap] = { 0 };
	return 0;
}

image-20220429092704366

在Linux环境下我们出现了两种共情况,我们分别列出.

跑得过,可以编译和运行.原因是Linux 中除了C标准之外,还应用的GNU标准的C,这里被gcc编译器认为是变常数组了.

int main()
{
	const int cap = 10;
	int arr[cap];
	return 0;
}

image-20220429101919829

出现报错 报错 : 可变大小的对象可能未初始化,这是由于gcc支持变长数组,变长数组规定 不能够初始化.

int main()
{
	const int cap = 10;
	int arr[cap] = {0};
	return 0;
}

image-20220429101956064

从这里就可以看出,const修饰的变量就是一个变量,那么const不就是一个没有用到的东西吗?非也非也,我们是不是有这种情况,我们定义了了一个变量,不希望自己或者其他人对他进行修改,该怎办?const就可以解决这个问题.

int main()
{
	const int cap = 10;
	cap = 20;
	return 0;
}

image-20220429093447305

const修饰变量的原理

我么就很疑惑,难道const修饰的变量就真的没有办法更改吗?要是不能修改,和常量又有什么区别!!!,所以说它是一定可以修改的,下面就是一种修改方法.我不直接改变它的值,我找到他所在的空间,我把它空间的里面的只给改了,这样就可以间接修改了const修饰的的变量.

int main()
{
	const int cap = 10;
	int* p = &cap;
	*p = 20;
	printf("%d\n", cap);
	return 0;
}

image-20220429094242059

到这里我们就要考虑const的原理了,用const修饰变量就像我们把一袋金子放到屋子里面,我们把屋子的门给锁上,这样就不害怕有小偷来偷走它了.但是现在的小偷很聪明,既然我从门进不去,但是我看到窗户没有上锁,我从这爬进去,虽然方式不同,但是我还是拿到了金子.const就相当于那把门锁.

可是这里给大家提出一个问题,为何这里我们不能进行偷家

const int flag = 1;


int main()
{
	
	int* const p = &flag;
	while (flag)
	{
		printf("%d \n", flag);
		*p = 0;
	}
	printf("-------------");

	return 0;
}

image-20230123103812596

那么我想的问的是const存在有什么意思呢?下面就是.

1、让编译器进行修改时的检查

2、让其他程序员看到,提醒他不要不要修改这个值

真正的常量

我们刚才谈了变量,这里给大家看看什么是真正的常量.像1,2,3…这些都是常量,这里还有一个字符串常量.const 的不能修改是指对编译器而言的,而“abcdef”是在字符串常量区,是系统不让修改的.

int main()
{
	char* p = "hello";//常量字符串
	*p = 'H';
    printf("%c", p);
	return 0;
}

image-20230123101508764

我们去gcc中测试一下,直接段溢出.

image-20230123101705367

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;
}

image-20220429103819898

但是p可以指向另外的地址

int main()
{
	int a = 0;
	int b = 20;
	const int* pa = &a;
	pa = &b;
	return 0;
}

image-20220429104050896

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;
}

image-20220429104623699

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;
}

image-20220429104823086

goto

很多公司确实禁止使用goto,不过,这个问题我们还是灵活对待,goto在解决很多问题是有奇效的。我们可以认为goto使用场景较少,一般不使用。但是必须得知道goto,需要的时候,也必须会用.这个我们可以认为是是程序执行的时候直接跳跃.

int main()
{
	goto end;
	printf("hello word\n");
end:
	printf("hello end\n");
	

	return 0;
}

image-20230123104104797

还有下面的一种情况,这里还可以跳到上面.

int main()
{
	int a = 10;
end:
	{
		printf("hello end\n");
		a = 0;
	}

	if (a)
	{
		goto end;
		printf("hello word\n");

	}

	return 0;
}

image-20230123104104797

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;
}

image-20220428220820054

这个结果很明显,编译就过去,那我们就不得不疑惑了,为什么void不可以定义变量,首先我们要明白一件事,变量的存在需要给变量开辟空间用来存储数据.void不能够开辟空间.

#include <stdio.h>

int main()
{
	printf("%d\n", sizeof(void));
	return 0;
}

image-20220429082411416

我们会发现,在VS2013上,void类型没有开辟空间,所以它是不能定义变量的.有的人可能会对Linux环境下感兴趣,我们也来看看吧.代码和上面的一样,这里我就给出结果了.

我们可以轻易地发现,在Linux环境下,void开辟了一个空间,这是不是意味着在Linux环境下,void可以定义变量,很抱歉,这还是不可以的,即使Linux开辟了空间,但是编译器会认为它是空类型,禁止给它定义变量.

那么Linux为何什么环境下可以开辟空间,实际上gcc编译器不仅仅支持C语言标准,它还扩充乐意GNU计划,里面的内容大家有兴趣看看读读文档.

image-20220429082923485

void*

一般而言,这就是我们接触到所有的void的应用了,不过要是你模拟实现过C语言的qsort,你会发现另一个应用,void*,我们也遇见过使用maollc或者realloc开辟空间的时候最好给他们强制类型转换成我们想要的指针类型,那么这里我们就会很好奇,malloc函数的返回类型是什么?为何可以变成我们想要的任意指针类型.我们看看它的函数.

image-20220428203457954

我们发现它的返回值是void* ,可是我们知道int*,float* ,那么请你告诉我void*究竟是神马玩意?

void* 也是指针,我们它可以接受任意类型的指针,也可以强制类型转换成任何类型的指针,由于我们都知道指针在32位平台下是4个字节,64位是8个字节.我们不知道malloc要开辟的空间指向什么类型,但是我们可以强制类型转换.

我们来看看void*在32位平台下占据多少个字节.

#include <stdio.h>
int main()
{
	printf("%d", sizeof(void*));
	return 0;
}

image-20220428220349071

void*是不是可以解引用

解引用的作用是使得指针变成相对应的类型,我们就开始疑惑了,void*是不是也可以解引用.

int main()
{
	//printf("%d\n", sizeof(void));
	int a = 0;
	void* pa = &a;
	*pa;
	return 0;
}

我们可以发现,在Linux和Windows环境下,void*都不能解引用.这一点很重要的.

image-20220429085602748

image-20220429085826059

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环境下是不会开辟空间的,它怎么跳?

image-20220429090440831

在Linux环境下就可以很好理解了,void开辟了一个空间,所以void*+1会跳过一个字节.

image-20220429091244021


const关键字

我想问一下,你在C语言中遇到过这个关键字字吗,你真的了解它的原理吗?还是说你就用它来修饰一个变量?今天我将带你好好的看看它详细的用法.

const修饰变量

这个我们知道,不就是修饰一个变量使它变成常变量,那么请你告诉我,常变量是变量还是常量?你怎么验证?

首先常变量是一个变量,只是拥有常量的属性,但是本质还是变量.,我们在C89标准下不支持变长数组,也就是说我们定义数组长度的时候必须是常量,要是常数变量是常量,那么编译器一定不报错.

int main()
{
	const int cap = 10;
	int arr[cap] = { 0 };
	return 0;
}

image-20220429092704366

在Linux环境下我们出现了两种共情况,我们分别列出.

跑得过,可以编译和运行.原因是Linux 中除了C标准之外,还应用的GNU标准的C

int main()
{
	const int cap = 10;
	int arr[cap];
	return 0;
}

image-20220429101919829

出现报错 报错 : 可变大小的对象可能未初始化,这是由于gcc支持变长数组,变长数组规定 不能够初始化为.

int main()
{
	const int cap = 10;
	int arr[cap] = {0};
	return 0;
}

image-20220429101956064

从这里就可以看出,const修饰的变量就是一个变量,那么const不就是一个没有用到的东西吗?非也非也,我们是不是有这种情况,我们定义了了一个变量,不希望自己或者其他人对他进行修改,该怎办?const就可以解决这个问题.

int main()
{
	const int cap = 10;
	cap = 20;
	//int arr[cap] = { 0 };
	return 0;
}

image-20220429093447305

const修饰变量的原理

我么就很疑惑,难道const修饰的变量就真的没有办法更改吗?要是不能修改,和常量又有什么区别!!!,所以说它是一定可以修改的,下面就是一种修改方法.我不直接改变它的值,我找到他所在的空间,我把它空间的里面的只给改了,这样就可以间接修改了const修饰的的变量.

int main()
{
	const int cap = 10;
	int* p = &cap;
	*p = 20;
	printf("%d\n", cap);
	return 0;
}

image-20220429094242059

到这里我们就要考虑const的原理了,用const修饰变量就像我们把一袋金子放到屋子里面,我们把屋子的门给锁上,这样就不害怕有小偷来偷走它了.但是现在的小偷很聪明,既然我从门进不去,但是我看到窗户没有上锁,我从这爬进去,虽然方式不同,但是我还是拿到了金子.const就相当于那把门锁.

const存在的意义

1、让编译器进行修改时的检查

2、让其他程序员看到,提醒他不要不要修改这个值

真正的常量

我们刚才谈了变量,这里给大家看看什么是真正的常量.像1,2,3…这些都是常量,这里还有一个字符串常量.

const 的不能修改是指对编译器而言的,而“abcdef”是在字符串常量区,是系统不让修改的

int main()
{
	char* p = "hello";//常量字符串
	*p = 'H';
    printf("%c", p);
	return 0;
}

image-20220429100808648

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;
}

image-20220429103819898

但是p可以指向另外的地址

int main()
{
	int a = 0;
	int b = 20;
	const int* pa = &a;
	pa = &b;
	return 0;
}

image-20220429104050896

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;
}

image-20220429104623699

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;
}

image-20220429104823086

总结:

const与谁靠的近,就修饰谁,谁就不可以再次改变


static关键字

关于staic关键字,我有很多想和大家分享的,它是在太让我们忽略了,即使是现在我还需要借助我以前的笔记来写这篇博客.我脑子就记住了一个,static修饰局部变量改变它的生命周期,不改变作用域.下面是我总结的一些static的作用.

  1. 修饰局部变量
  2. 修饰全局变量
  3. 修饰函数

修饰局部变量

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;
}
img
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;
}
img

从现象我们可以看出,static 修饰的变量的 空间没有被销毁,否则不会打印出3 4 5… 我们证明一下,看看打印出来a的值是

img

可以看出,a的值是确定值,其地址没有被销毁,那么我们可以得到static改变的局部变量的生命周期

那么作用域变了没有? ------- 作用域没有改变.

img

为什么static可以改变局部变量的生命周期?

static修饰的局部变量,会在全局数据区或者静态数据区开辟空间(编译器的不同),这就造成了static可以改变局部变量的生命周期。

详细的可以看一下C程序地址空间

img

为什么函数和全局变量可以跨文件访问

在谈这个之前,我们需要说一说多文件,为何我们要定义几个文件,我们可以试想一下这样的场景,我们写的函数很多,当我们使用函数的时候发现要找好久,有时还不知道函数的参数和返回值,我们是不是可以定义一个头文件,把自己的写的函数都声明出来.

  • .h:我们称之为头文件,一般包含函数声明,变量声明,宏定义,头文件等内容(header)
  • .c: 我们称之为源文件,一般包含函数实现,变量定义等

我们写的大型项目一般都是多文件项目,文件与文件之间一定要可以进行跨文件访问,否则,我们不能跨文件,那么“交互”的成本就比较高。但总有些代码需要隐藏,所以出现了static这个关键字。

修饰全局变量

static修饰的全局变量只能在本文件中内被访问,不能被外部文件直接访问.

未用static修饰

img

用static修饰

全局变量拥有外部链接属性,被static修饰后,外部链接属性好像消失了

img

修饰函数

先不来谈这个,我们先看卡这种情况,我们在test.c里面定义一个函数,在main.c里面直接调用,什么都不做,这个代码会不会报错.

//test.c
void show()
{
	printf("你可调用到我\n");
}

//main.c

#include <stdio.h>

int main()
{
	show();
	return 0;
}

image-20220429115108265

image-20220429115325825

我在mian.c里面都没有声明这个函数,为毛还会出现正确的关键字?这是怎么回事.这是由于函数具有外部链接属性,当我们连接时,编译器会自动去寻找这个函数.

static修饰的函数和全局变量一样,只能在本文件中内被访问,不能被外部文件直接访问

未用static修饰

img

用static修饰

img

那被static修饰的函数如何可以间接访问

在static修饰的函数的文件内,可以再写一个函数调用被static修饰的函数,在外部文件调用该函数,就可以间接调用static修饰的函数了

img

总结

  1. static 修饰局部变量,改变的是生命周期,不改变作用域。
  2. static修饰函数,目的在于封装,提高代码的安全性。使用户只能使用该文件,但是不能随意修改里面的代码,static提供项目维护、安全保护。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/353157.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

win系统电脑如何打开sketch?

2 个方法快速使用 Windows 系统打开 Sketch 文件&#xff1a;使用 Adobe XD 打开 Sketch 文件或者使用浏览器中就能做设计的即时设计直接打开 Sketch 文件。众所周知&#xff0c; Sketch 只能在 Mac 电脑上使用&#xff0c;因此只有借助 XD 这种能在 Windows 系统启动的软件工具…

LSTM原理解析

一、背景 前文讲了RNN的基本原理&#xff0c;可以发现RNN是一个比较简单的神经网络结构&#xff0c;虽然为文本和时间序列的建模提供了一个很好的思路&#xff0c;但是也有一定的局限性。最直观的就是使用了Tanh函数造成梯度消失的问题。 根据Tanh的性质&#xff0c;很容易出…

树莓派Bullseye版本更新

1、参考链接&#xff1a;树莓派11bullseye换源/Opencv安装_所罗门大帝的博客-CSDN博客 2、参考链接&#xff1a;https://blog.csdn.net/weixin_39589455/article/details/124514099 步骤1、使用镜像帮助&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/help/raspbian/ 步骤…

面试题 链表相交 -剑指offer简单

面试题 链表相交 题目链接 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 注意&#…

DIY生日蛋糕笔记

自制6寸生日蛋糕笔记 实验环境&#xff1a; 长帝CRTF32PD搪瓷烤箱32升&#xff0c; 九阳电动打蛋器&#xff0c; 裱花盘一套 蛋糕盒子 称重器 硅胶刀 两个大碗1号和2号。 材料&#xff1a; 参考&#xff1a; https://www.bilibili.com/video/BV1t34y1Z7mL/?spm_id_from333…

揭开苹果供应链,如何将其命运与中国深度捆绑

前 言 诺基亚在2007年时拥有9亿用户&#xff0c;在手机市场上占据主导地位&#xff0c;福布斯在当时以“谁能赶上手机之王&#xff1f;”为标题刊登了一篇关于该公司的报道&#xff0c;与此同时&#xff0c;苹果公司推出了iPhone系列产品。16年后&#xff0c;苹果公司以充足的…

如何解锁华为手机PIN/图案/指纹/人脸锁?

您的手机存储主要数据&#xff0c;因此为您的设备设置安全代码让您高枕无忧。屏幕和生物识别锁可在手机丢失时保护您的手机&#xff0c;并使其在被盗时无法访问。每次您需要密码来解锁手机时&#xff0c;但如果您被锁在密码之外怎么办&#xff1f; 今天的话题将独家帮助华为用…

ChatGPT引发的新一轮网络安全威胁,要如何应对?

近期随着美国ChatGPT横空出世&#xff0c;沉寂一时的人工智能领域再次掀起波澜&#xff0c;引起社会的广泛关注&#xff0c;仅在短短数月内&#xff0c;ChatGPT的注册用户就超过数亿。作为一款免费的聊天机器人&#xff0c;它几乎可以回答任何问题。和搜索引擎不同的是&#xf…

JavaSE 文件内容的读写

读文件和写文件有俩种风格的操作&#xff08;俩组不同的类&#xff09; 字节流 InputStream&#xff08;负责读&#xff09;/ OutputStream &#xff08;负责写&#xff09;~~针对二进制文件进行读写&#xff0c;操作基本单位是字节。字符流 Reader(负责读&#xff09;/ Write…

vue初识

第一次接触vue&#xff0c;前端的html,css,jquery,js学习也有段时间了&#xff0c;就照着B站的视频简单看了一些&#xff0c;了解了一些简单的用法&#xff0c;这边做一个记录。 官网 工具&#xff1a;使用VSCode以及Live Server插件&#xff08;能够实时预览&#xff09; 第…

【龙芯1B】:LCD显示图片文字背景色前景色、小创语音控制lcd显示、数码管倒计时

项目场景&#xff1a; 闲来无事&#xff0c;写了几个关于嵌入式技能大赛的任务。希望对大家有所帮助。本文开发板由百科荣创的龙芯1b开发板支持&#xff0c;关于嵌入式技能大赛的开发板。 LCD显示图片&文字&背景色&前景色小创语音控制lcd显示数码管倒计时LCD显示图…

创建自己的脚手架(一)

创建自己的脚手架 脚手架基本框架 使用pnpm init 创建环境 取一个自己喜欢的名字 这里叫gucli 生成 package.json文件的内容 {"name": "gucli","version": "1.0.0","description": "","main": "…

【JavaSE】Java9Java10Java11新特性(687~717)

687.复习&#xff1a;动态代理 688.复习&#xff1a;Lambda表达式 689.复习&#xff1a;函数式接口 690.复习&#xff1a;方法引用与构造器引用 691.复习&#xff1a;Stream API 692.复习&#xff1a;Optional类的使用 693.jdk版本更新说明 JDK 9 的发布 经过4次跳票&#x…

Leetcode力扣秋招刷题路-0074

从0开始的秋招刷题路&#xff0c;记录下所刷每道题的题解&#xff0c;帮助自己回顾总结 74. 搜索二维矩阵 编写一个高效的算法来判断 m x n 矩阵中&#xff0c;是否存在一个目标值。该矩阵具有如下特性&#xff1a; 每行中的整数从左到右按升序排列。 每行的第一个整数大于前…

《MySQL学习》 事务的隔离

一.快照读与当前读 众所周知,MySQL默认的隔离级别为可重复读&#xff08;RR&#xff09;&#xff0c;开启一致性视图后&#xff0c;事务提交前后数据都是不变的。 事务 T 启动的时候会创建一个视图 read-view&#xff0c;之后事务 T 执行期间&#xff0c;即使有其他事务修改了…

数值卡,让数据可视化玩出新花样丨三叠云

数值卡 路径 仪表盘 >> 仪表盘设计 功能简介 1. 数值卡增加「数值标题」、「图标」、「进度条」功能&#xff0c;使得应用场景更为广泛&#xff0c;实现数据可视化&#xff0c;让用户能够轻松地获取、处理信息。 2.「数据模型」支持0个维度1个指标、1个维度1个指标。…

EMC简介(文末有易灵思核心板)

EMC测试又叫做电磁兼容&#xff08;EMC&#xff09;&#xff0c;指的是对电子产品在电磁场方面干扰大小&#xff08;EMI&#xff09;和抗干扰能力&#xff08;EMS&#xff09;的综合评定&#xff0c;是产品质量最重要的指标之一&#xff0c;电磁兼容的测量由测试场地和测试仪器…

嵌入式Linux文件系统的介绍

学习Linux都是跟文件打交道&#xff0c;却不知道文件系统下的目录存放的内容&#xff0c;有点说不过去&#xff0c;特意准备了这篇文章供大家阅读&#xff0c;哪怕有一点点帮助&#xff0c;都是对我的鼓励。 根文件系统一般应该比较小&#xff0c;因为包括严格的文件和一个小的…

手机子品牌的“性能战事”:一场殊途同归的大混战

在智能手机行业进入存量市场后&#xff0c;竞争更加白热化。当各国产手机品牌集体冲高端&#xff0c;旗下子品牌们也正厮杀正酣&#xff0c;显现出刀光剑影。处理器、屏幕、内存、价格等各方面无不互相对标&#xff0c;激烈程度并不亚于高端之争。源于OPPO的中端手机品牌realme…

input专题--独占事件

最近在群里聊天&#xff0c;遇到一个哥们描述的一个问题&#xff1a; 大家好&#xff0c;请教一个关于文件的问题。有几个进程打开了/dev/input 设备&#xff0c;都可以收到数据&#xff0c;又来了一个进程x&#xff0c;打开了这个设备&#xff0c;不知道采用了什么方式&#…