基础知识
*星号:在C语言中表示乘号,也可以表示指针符号。
&取地址符号:加载变量的前面可以取变量地址。
左值:在赋值运算符左边,表示变量名所代表的内存空间
右值:在赋值运算符的右边,表示变量名所代表的内存空间中存放的值
什么是指针?
答:指针本质上就是一个变量,完整的名字应该是指针变量,简称指针。一个指针变量存储的是一个内存地址(32位系统为4字节)。
为什么需要指针?
答:指针的存在是为了实现间接访问。间接访问(间接寻址)是CPU设计时候决定的,所以决定了汇编必须能实现间接寻址,在汇编之上的C语言也必须能实现间接寻址。对于更高级的语言诸如C#、java这类语言帮我们封装了,所以没有指针也可以实现间接寻址。
指针变量的使用步骤:定义指针变量、关联指针变量、解引用。
#include<stdio.h> int main(void) { int a=3; int *p; //定义指针变量 p = &a; //关联指针变量 *p =5 //解引用 }
在函数种定义指针变量时,遵循局部变量的一般规律。也就是如果定义时没有初始化p的值,则值是随机的(上次使用后未清0的值)。因此就引出了野指针的问题。
什么是野指针?
野指针是指指针所指向的位置是不可知的。在函数内部定义指针变量时,没有对变量初始化就会出现野指针的情况。这时候指针变量p的值是随机的,也就是指针指向的变量是随机的。可能出现如下情况,第一:指向一个不可访问的地址。(程序会触发段错误)
第二:指向一个可用的、而且没什么特别意义的空间。(程序不报错,但实际上错了。相当于错误被掩盖了)
第三:情况就是指向了一个可用的空间,而且这个空间其实在程序中正在被使用。(导致程序出现离奇错误)
如何避免野指针?
举例:
int *p=NULL; //第一点:定义指针时,同时初始化为NULL
P = &a; //第二点:在指针使用之前,将其赋值绑定给一个可用地址空间(假设之前已定义了一个变量a)
if(NULL != P) //第三点:在指针解引用之前,先去判断这个指针是不是NULL
{
*P = 4;
}
P = NULL; //第四点:指针使用完之后,将其赋值为NULL
一般的,在中小型程序中,自己水平可以把握的情况下,不必严格参照这个标准;但是在大型程序,或者自己水平感觉不好把握时,建议严格参照这个方法。
NULL是什么?
#ifdef _cplusplus // 定义这个符号就表示当前是C++环境
#define NULL 0 // 在C++中NULL就是0
#else
#define NULL (void *)0 // 在C中NULL是强制类型转换为void *的0
#endif
const关键字与指针
第一种:const int *p; //指针变量p指向的是一个int类型的常量
第二种:int const *p; //指针变量p指向的是一个int类型的常量
第三种:int * const p; //指针变量p本身是一个常量,指向的那个值是个int型变量
第四种:const int * const p; //指针变量p是个常量,它指向的是一个int型常量
数组中几个关键符号(a a[0] &a &a[0])的理解
(1)a:数组名。a做左值时表示整个数组的所有空间,a不能做左值;a做右值表示数组首元素的首地址<==>&a[0];
(2)a[0]:数组的首元素,也就是数组的第0个元素。做左值时表示数组第0个元素对应的内存空间(连续4字节);做右值时表示数组第0个元素的值;
(3)&a:就是数组名a取地址,字面意思来看就应该是数组的地址。&a不能做左值;&a做右值时表示整个数组的首地址。
(4)&a[0]字面意思就是数组第0个元素的首地址。不能做左值,做右值时表示数组首元素的地址。
数组与指针
(1)数组元素使用时不能整体访问,只能单个访问。访问方式有2种:数组形式和指针形式。
(2)数组格式访问数组元素是:数组名[下标]; (注意下标从0开始)
(3)指针格式访问数组元素是:*(指针+偏移量); 如果指针是数组首元素地址(a或者&a[0]),那么偏移量就是下标;指针也可以不是首元素地址而是其他哪个元素的地址,这时候偏移量就要考虑叠加了。
(4)数组下标方式和指针方式均可以访问数组元素,两者的实质其实是一样的。在编译器内部都是用指针方式来访问数组元素的,数组下标方式只是编译器提供给编程者一种壳(语法糖)而已。所以用指针方式来访问数组才是本质的做法。
举例:
int a[5] = {1, 2, 3, 4, 5}; int *p; p = a; printf("*(p+1) = %d. ", *(p+1));
类型匹配
char *p; int a[5]; p = a; 类型匹配,这里的a做右值表示&a[0]是数组首元素首地址 int *p; int a[5]; p = &a; 类型不匹配,这里&a做右值,a本身表示整个数组,取地址符&加在前面表示数组的地址
define与typedef关键字
#define dpChar char *
typedef char *tpChar;
dpChar p1, p2; sizeof(p1) sizeof(p2)
tpChar p3, p4; sizeof(p3) sizeof(p4)
变量传参实际上只是把值赋值了一份传进去,也就是传说中的传值调用
void func1(int a) { printf("a=%d. ",a); } int main(void) { int a=4; func1(a); return 0; }
函数传参
(1)数组名作为函数形参传参,实际传递的是整个数组首元素的首地址,因为传参为传值。void func(int a[]){}
(2)指针作为函数形参传参,和数组作为函数形参的实现方式一样。void func1(int *a){}
(3)结构体变量作为函数形参,和普通变量传参时表现一样。由于结构体很大我们通常也是传地址的
一般地函数传参的时候,分为输入性参数和输出型参数,我们通常在输入性参数加上const 关键字,表明他不需要更改。
(本文内容参考了朱有鹏C语言高级专题)