目录
一: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无意义。