C最近要整理一些与内存相关的知识,所以也就顺便复习了一下,下面是一些之前没怎么关注的小知识,记录于此。
1.定义与声明
定义:所谓的定义就是(编译器)创建一个对象,为这个对象分配一块内存并给它取上一个名字,这个名字就是我们经常所说的变量名或对象名。只能定义一次,但可以声明多次。
定义和声明最重要的区别:定义创建一个对象并为这个对象分配了内存;声明不分配内存
1.1函数的定义与声明
函数声明:返回值数据类型 函数名(形参类型 [形参名]); 切记:无函数体,即使是空函数体也不行,声明之后有分号;
函数定义:返回值数据类型 函数名(形式参数说明) {说明语句 执行语句}
说明:
1.函数返回值不能是数组,也不能是函数,除此之外任何合法的数据类型都可以是函数的返回值类型。函数返回值的类型可以省略,当不指明函数返回值类型时,默认为int; return 语句不可返回指向“栈内存”的“指针”,因为该内存在函数体结束时被自动销毁。例如:
char * Func(void) { char str[30]; return str; }
str 属于局部变量,位于栈内存中,在Func 结束的时候被释放,所以返回str 将导致错误。
2.函数名本身代表了该函数对应的可执行代码的入口地址;
3.形式参数简称“形参”,形参之间用逗号隔开;
1.2 变量的定义与声明
变量的声明同时具备两个条件:1) extern 2)无显式赋值
1.3 extern关键词
extern,外面的、外来的意思。那它有什么作用呢?举个例子:假设你在大街上看到一个黑皮肤绿眼睛红头发的美女(外星人?)或者帅哥。你的第一反应就是这人不是国产的。extern 就相当于他们的这些区别于中国人的特性。extern 可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中。
关于extern关键字,我们在之前的博客中也记录过,对于变量声明,必须加extern,否则编译器不能识别,对于函数声明,虽然不加,编译器也可以识别,但最好要加上,因为便于代码后面的阅读和理解,加上extern,阅读时,我们就很容易区别哪些函数是在该文件下定义的,哪些是在其他文件下定义的。
2.含有函数声明的头文件应该被包含在定义函数的源文件中
3.const : readonly; const关键字就是只读的意思,通常在形参中使用,防止在函数体内被修改和污染。
4.C语言中static关键字的作用
1)修饰变量,静态全局和静态局部变量
静态变量又分为局部和全局静态变量,但它们都存在内存的静态区。
静态局部变量,在函数体里面定义的,就只能在这个函数里用了,同一个文档中的其他函数也用不了。由于被static 修饰的变量总是存在内存的静态区,所以即使这个函数运行结束,这个静态变量的值还是不会被销毁,函数下次使用时仍然能用到这个值。
静态全局变量,作用域仅限于变量被定义的文件中,其他文件即使用extern 声明也没法使用它。准确地说作用域是从定义之处开始,到文件结尾处结束,在定义之处前面的那些代码行也不能使用它。想要使用就得在前面再加extern ***。恶心吧?要想不恶心,很简单,直接在文件顶端定义不就得了。
2)修饰函数,被修饰的函数称为内部函数,这里的内部指文件内部,即定义该函数的文件
5.数据类型
基本类型:short(2); int(4); long(4); char(1); float(4); double(8); 单位:bytes (32位系统下)
构造类型:array; struct; enum; union;
指针类型:4bytes
空类型 :void
5.sizeof关键字
sizeof 在计算变量所占空间大小时,括号可以省略,而计算类型大小时不能省略。
int i; sizeof(int); sizeof i; sizeof(i);
6.条件循环语句查漏补缺
1) C 语言有这样的规定:else始终与同一括号内最近的未匹配的if 语句结合。If()语句后是没有;的,但是如果加了;相当于if(){;},空语句
2) switch(变量表达式): case 常量表达式(整型/字符型/枚举类型) : 语句; break; 没有break就会从匹配行起,继续执行
3) break关键字很重要,表示终止本层循环。现在这个例子只有一层循环,当代码执行到break 时,循环便终止。如果把break 换成continue 会是什么样子呢?continue 表示终止本次(本轮)循环。当代码执行到continue 时,本轮循环终止,进入下一轮循环。
7.struct 与class 的区别
在C++里struct关键字与class 关键字一般可以通用,只有一个很小的区别。struct 的成员默认情况下属性是public 的,而class 成员却是private 的。很多人觉得不好记,其实很容易。你平时用结构体时用public 修饰它的成员了吗?既然struct 关键字与class 关键字可以通用,你也不要认为结构体内不能放函数了。
8. a 和&a 的区别
看这个例子:
main() { int a[5]={1,2,3,4,5}; int *ptr=(int *)(&a+1); printf("%d,%d",*(a+1),*(ptr-1)); }
打印出来的值为多少呢? 这里主要是考查关于指针加减操作的理解。对指针进行加1 操作,得到的是下一个元素的地址,而不是原有地址值直接加1。所以,一个指向类型T 的指针的移动,以sizeof(T)为移动单位。因此,对上题来说,a 是一个一维数组,数组中有5 个元素; ptr 是一个int 型的指针。
&a + 1: 取数组a 的首地址,该地址的值加上sizeof(a) 的值,即&a + 5*sizeof(int),也就是下一个数组的首地址,显然当前指针已经越过了数组的界限。(int *)(&a+1): 则是把上一步计算出来的地址,强制转换为int * 类型,赋值给ptr。*(a+1): a,&a 的值是一样的,但意思不一样,a 是数组首元素的首地址,也就是a[0]的首地址,&a 是数组的首地址,a+1 是数组下一元素的首地址,即a[1]的首地址,&a+1 是下一个数组的首地址。所以输出2。*(ptr-1): 因为ptr 是指向a[5],并且ptr 是int * 类型,所以*(ptr-1) 是指向a[4] ,输出5。
9.NULL
在C语言中,NULL被宏定义为0
#define NULL 0
所以,对于大小写敏感的C语言,一定在为指针初始化时,不要写成Null或者null,因为宏定义是固定在那里的,只识别NULL。
10.数组指针和指针数组
下面到底哪个是数组指针,哪个是指针数组呢:
A),int *p1[10];
B),int (*p2)[10];
“[]”的优先级比“*”要高。p1 先与“[]”结合,构成一个数组的定义,数组名为p1,
int * 修饰的是数组的内容,即数组的每个元素。那现在我们清楚,这是一个数组,其包含10 个指向int 类型数据的指针,即指针数组。至于p2 就更好理解了,在这里“()”的优先级比“[]”高,“*”号和p2 构成一个指针的定义,指针变量名为p2,int 修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组。那现在我们清楚p2 是一个指针,它指向一个包含10 个int 类型数据的数组,即数组指针。
参考书籍:
《C语言程序设计教程》 李凤霞 北京理工大学出版社
《C语言深度剖析》 陈正冲