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