C深剖关键字(3)

目录

一:void关键字

二:return关键字

三:const关键字


一:void关键字

问题:void能否定义变量?

 从图中不难发现,void定义x出错,可原因是什么呢?

定义变量的本质:开辟空间

而void作为空类型,理论上是不应该开辟空间的,即使开了空间,也仅仅作为一个占位符看待

所以,既然无法开辟空间,那么也就无法作为正常变量使用,既然无法使用,编译器干脆不让他定义变量。

void的大小在不同编译器下是不一样的

vs2013中,sizeof(void)=0

Linux中,sizeof(void)=1(但编译器依旧理解成,无法定义变量)

void本身就被编译器解释为空类型,强制的不允许定义变量。

void修饰函数返回值:

看代码:

#include<stdio.h>
void test()
{
	printf("hello test\n");
	return 1;
}
int main()
{
	test();
	return 0;
}

但是当我们去掉void返回值的时候再试试:

#include<stdio.h>
test()
{
	printf("hello test\n");
	return 1;
}
int main()
{
	int a = test();     // hello test
	printf("%d\n", a);  // 1
	return 0;
}

我们发现编译器可以正常运行没有报错,由此得到以下结论:

//如果自定义函数,或者库函数不需要返回值,那么就可以写成void

//那么问题来了,可以不写吗?不可以,自定义函数的默认返回值是int(这个上述验证)

//所以,没有返回值,如果不写void,会让阅读你代码的人产生误解:他是忘了写,还是想默认int?

//结论:void作为函数返回值,代表不需要,这里是一个"占位符"的概念,是告知编译器和给阅读源代码的工程师看的。

void修饰函数参数:

看代码:

#include <stdio.h>
int test1() //函数默认不需要参数
{
	return 1;
}
int test2(void) //明确函数不需要参数
{
	return 1;
}
int main()
{
	//test1的形参列表为空,可不可以给它传参呢?
	test1(1, 2, 3, 4); //依旧传入参数,编译器不会告警或者报错
	test2(1, 2, 3, 4); //依旧传入参数,编译器会告警(vs)或者报错(gcc)
	return 0;
}

总结:

结论:如果一个函数没有参数,将参数列表设置成void,是一个不错的习惯,因为可以将错误明确提前发现

另外,阅读你代码的人,也一眼看出,不需要参数。相当于"自解释"。

void充当函数的形参列表,告知用户or编译器,该函数不需要传参

void指针:

void不能定义变量,那么void*呢?

#include<stdio.h>
int main()
{
	void* p = NULL;
	return 0;
}

这里编译器并没有报错,为什么void*可以呢?

因为void*是指针,是指针,空间大小就能明确出来

只要是指针,在32位就占4个字节,在64位就占8个字节。

void* 能够接受任意指针类型:&&  void*可以接受任意指针类型:

#include<stdio.h>
int main()
{
	void* p = NULL;
	double* x = NULL;
	int* y = NULL;
	x = p;
	y = p;
	return 0;
}

此代码块运行编译器没有报错。

x=p和y=p相当于把p指针变量的值赋给x和y,而x的类型是double*,y的类型是int*p的类型是void*,等号左右两边类型不一样。

结论:void*可以被任何指针类型接受

当我们把x=p和y=p调换位置呢。

#include<stdio.h>
int main()
{
	void* p = NULL;
	double* x = NULL;
	int* y = NULL;
	p = x;
	p = y;
	return 0;
}

编译器依旧没有报错。

结论:void*可以接受任意指针类型(常用)

void * 定义的指针变量可以进行运算操作吗?

#include<stdio.h>
int main()
{
	//void* p = NULL;
	int* p = NULL;
	p++;
	p--;
	// 对于整型指针是没有问题的
	//而void*是空类型,在vs大小为0,而使用p++或--需要明确大小,故不行

	return 0;
}

// 对于整型指针是没有问题的
//而void*是空类型,在vs大小为0,而使用p++或--需要明确大小,故不行

void*可以直接解引用吗?

#include<stdio.h>
int main()
{
	//  void* p = NULL;
	//  *p; // err
	int a = 10;
	void* p = (void*)&a;
	// *p; // err
	*p = 20;  // err
	return 0;
}

原因:

p是void*指针类型,那么解引用*p它指向的目标就是void,而其空间大小无法得知

结论:void*不能直接解引用

二:return关键字

看代码:

#include<stdio.h>
char* show()
{
    char str[] = "helloworld";
	return str;
}
int main()
{
	char* s = show();
	printf("%s\n", s);

	return 0;
}

运行起来确是一串乱码 ,为什么呢?

此时在函数内定义的char str[] = "hello bit";数组是在栈上开辟的空间,它本质上是一份临时空间,函数调用的时候就在栈上开辟空间,函数调用完毕,栈结构,临时变量,数据就会被释放掉,所以打印乱码。

C语言有字符串但没有字符串类型

计算机如何删除数据呢?

//计算机中,释放空间是否真的要将我们的数据全部清0/1 ?

//计算机中清空数据,只要设置该数据无效即可

重新分析下上述的代码:

show函数是函数调用,调用时就要开辟空间,在这块空间中要开辟一块数组空间,而这块空间是在哪开辟的呢?接下来需要回顾下曾经讲过的内容。地址空间划分有如下几块:

 调用函数,形成栈帧,函数返回,释放栈帧,但是数据并没有被清空,仅仅表明这块空间是可被覆盖的,printf也是函数,调用printf形成栈帧,返回printf释放栈帧,形成新的栈帧就要覆盖原先老的栈帧,所以helloworld字符串便不存在

//为什么临时变量具有临时性!

//栈帧结构在函数调用完毕,需要被释放

#include<stdio.h>
int GetData()
{
    int x = 0x11223344;
    printf("run get data!\n");
    return x;
}
int main()
{
    int y = GetData();
    printf("ret:%x\n", y);

    return 0;
}

既然x是函数定义的变量,具有临时性,那么这个临时变量在函数退出的时候,应该被释放,但为什么还能正常打印呢?

通过查看汇编可以看到,对于一般内置类型,寄存器eax可以充当返回值的临时空间

函数的返回值,通过寄存器的方式,返回给函数调用方! 

三:const关键字

const修饰的变量是不可直接被修改!!!

#include<stdio.h>
int main()
{
	const int a = 10;
	// 等价于: int const a = 10;
	a = 20;  // err
	return 0;
}

但是可以通过指针的方式间接修改!!!

 那const修饰变量,意义何在?

1. 让编译器进行直接修改式检查

2. 告诉其他程序员(正在改你代码或者阅读你代码的)这个变量后面不要改哦。也属于一种“自描述”含义

const修饰的变量,可以作为数组定义的一部分吗?

#include<stdio.h>
int main()
{
	const int n = 100;
	int arr[n];  // err 编译器报错
	return 0;
}

//在vs2013(标准C)下直接报错了,但是在gcc(GNU扩展)下,可以,主要是不同编译器对C语言的标准不同。

//但我们一切向标准看齐,不可以。

const修饰数组:

#include<stdio.h>
int main()
{
	const int arr[] = { 1,2,3,4,5 };
	arr[0] = 0;   //err
  	arr[1] = 0;   //err
	arr[2] = 0;   //err
	arr[3] = 0;   //err
	return 0;
}

编译器报错,const修饰数组的话,代表的是数组的每一个元素都必须是不可被修改的,或者是只读数组

const修饰指针:

看代码:先总体了解下:

#include<stdio.h>
int main()
{
	int a = 10;
	int* p = &a;
	*p = 100;  // *p=100对p进行解引用,代表的是p所指向的目标,所以*p就是a,换句话说是把a的值改为100
	p = 100;   // p=100,p本身也是一个变量,本来p指向a变量的,现在p指向地址为100的一个变量
	return 0;
}

// *p=100对p进行解引用,代表的是p所指向的目标,所以*p就是a,换句话说是把a的值改为100

// p=100,p本身也是一个变量,本来p指向a变量的,现在p指向地址为100的一个变量

情形一:

#include<stdio.h>
int main()
{
	int a = 10;
	const int* p = &a;	  //p指向的变量不可直接被修改
	*p = 100;  // err    
	p = 100;   
	return 0;
}

此时,*p=100;编译器报错,理由如下:

// 此时的const修饰的是*p,不是p,也就是说不是p无法改变,而是p指向的内容无法改变

// 此时如果对p进行解引用,那么这个值是不能被修改的,换言之p指向的变量不可被修改,p无法被解引用后充当左值

// 所以p=100不会报错

情形二:

{
	int a = 10;
	int const * p = &a; //p指向的变量不可直接被修改
	*p = 100;  // err 
	p = 100;   
	return 0;
}

此情况和上述第一种一样,const修饰的都是*p而不是p,只不过将const挪位到int的右边,但不影响结果,理由和情形一相同。

情形三:

#include<stdio.h>
int main()
{
	int a = 10;
	int* const p = &a;  //p的内容不可直接被修改,p指向不能改
	*p = 100;  
	p = 100;   // err
	return 0;
}

int* const p = &a;  //此时的const在*的右侧,在p的左侧,则这个const修饰的是p变量,这样写和const int a = 10;没有差别,则就表明p的值不可直接被修改,p指向不能改

*p = 100;  // 但是可以间接修改,此时*p指向a,a可以被修改,*p=100;是没有问题的

而p=100;就是错的。

情形四:

#include<stdio.h>
int main()
{
	int a = 10;
	const int* const p = &a; 
	*p = 100;  // err
	p = 100;   // err
	return 0;
}

const int* const p = &a; //最左边的const指向*,代表p指向的不可以直接被修改,而离p最近的const修饰的是p,代表的是p的指向不能被修改

所以*p=100和p=100都是错的。

const修饰函数参数:

#include<stdio.h>
void show(const int* _p)
{
	printf("value:%d\n", _p);
}
int main()
{
	int a = 10;
	int* p = &a;
	show(p);
	return 0;
}

一般我们在设计的时候,因为show函数从设计角度本质就是打印,内部的打印就不会对传的参数进行修改,为了写出严谨的代码,应该在传参的时候带上const,代表的是此时_p指针变量所指向的内容不可改,也就是对应的a是不可改的

const修饰函数返回值:

代码解释:

#include<stdio.h>
//告诉编译器,告诉函数调用者,不要试图通过指针修改返回值指向的内容
const int* GetVal()
{
	static int a = 10;
	return &a;
}
int main()
{
    // int *p = test();   //有告警
	const int* p = GetVal();  //需要用const int*类型接受
    *p=200; //err  //这样,【在语法/语义上】,限制了,不能直接修改函数的返回值
	return 0;
}
//一般内置类型返回,加const无意义。
  • 31
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 19
    评论
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三分苦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值