大神们经常说C语言是一种底层语言,不像python啊,java啊这种高级编程语言。一直理解的不大好,也不明白为啥C语言就底层了。只是觉得老拿C语言做单片机嵌入式的开发,是不是就是底层了。最近看数据结构和算法,一会一个指针一会一个指针的总是弄混,感觉对指针有一种若即若离的感觉,既陌生有熟悉。决定重新回过头来看他。
1指针
- 地址和指针有着千丝万缕的关系,计算机内存中的每个位置都有一个地址标识,C语言中用指针来表示地址,声明一个指针变量并不会自动给他分配任何内存。在对指针进行间接访问前,指针必须初始化:要么指向现有内存(赋值),要么动态分配新的内存(malloc)。
- C标准定义了NULL指针,它作为一个特殊的指针常量,表示不指向任何位置,因而对一个NULL指针进行解引用操作同样也是非法的。因而在对指针进行解引用操作的所有情形前,如常规赋值、指针作为函数的参数,首先必须检查指针的合法性- 非NULL指针。
- 所有的指针进行显示的初始化是种好做法
如果知道指针被初始化为什么地址,就该把它初始化为该地址,否则初始化为NULL
在所有指针解引用操作前都要对其进行合法性检查,判断是否为NULL指针,这是一种良好安全的编程风格
1.1指针运算
- 自增自减:指向下一个地址或指向上一个地址
- 同类型指针相减:如果两个指针指向同一个数组,相减的结果就是两个指针之间的元素数目。注意:指针相加没有意义;不同类型的指针相减也没有意义
- 指针加上或减去一个整型值:如一个float类型的指针加3表示指针的值增加3个float类型的大小。
- 如果对一个指针进行减法运算,产生的指针指向了数组中第1个元素前面的内存位置,那么它是非法的。
- 加法运算稍微不同,如果产生的指针指向了数组中最后一个元素后面的那个内存地址,它是合法的,但不能对该指针执行解引用操作,不过之后就不合法了(这和STL中迭代器尾部元素可指向尾部元素的下一个位置是一样的道理)
插播:typedef 和#define有什么区别呢?
#define 是预处理指令,在编译预处理时进行简单地替换 ,不做正确性检查。
#define A 3+4
在程序中遇到3*A则会将A进行简单的替换->3*3+4=13
再例如:
#define int_ptr int* int_ptr a,b; //a是int*类型的,而b是int型的
typedef int* int_ptr int_ptr a,b; //a和b都是int*类型的
typedef是在编译时处理的,他是在自己的作用域里给一个已经存在的类型一个别名。
typedef char ElemType 把一个char型起一个别名叫ElemType,这样的好处是,当你想将程序中的数据类型换成int型的时候,不用一个一个修改,只需修改typedef char ElemType为typedef int ElemType即可。
1.2void *指针
C中提供一个特殊的指针类型: void *,它可以保存任何类型对象的地址。
void *表明该指针与一地址值相关,但不清楚存储在此地址上的对象的类型。void *指针只支持以下几种操作:
- 与另一个指针比较
- 给另外一个void *指针赋值
- void *指针当函数参数或返回值
不允许使用void *指针操作它指向的对象,值得注意的是函数返回void *类型时返回一个特殊的指针类型,而不是向返回void 类型那样无返回值。
1.3函数指针和指针函数
- 函数指针 int* f(int a, int b):本质是个指针,它指向的是函数,指向函数的指针包含了函数的地址,可以通过它来调用函数。
- 指针函数 int (*f)(int a, int b):本质是个函数,它的返回值是某个类型的指针
2 数组和指针
知乎上有个大神说:在软开行业里有一句话叫没有什么是不能通过增加一个抽象层解决的。比如说,数组就是对“一系列连续内存单元”的抽象,它的外观表现为“一个固定大小的容器”知乎连接。从这个角度上来理解,似乎数组比指针更“高级”一些,指针相对更底层。
2.1数组名
- 声明时:数组的属性和指针的属性不同,在声明数组时,同时分配了用于容纳数组元素的空间;而声明一个指针时,只分配了用于容纳指针本身的空间。
- 表达式中:需要注意,数组名是指向数组第一个元素的首地址,其本质是一个常量指针(常指针),指针自身的值不能被改变,如声明一个数组:int arr[3]={1,2,3},则不能出现arr++,或arr+=3这类语句。但是可以有int num=*(arr+2)指向第三个元素
- 通过数组名引用数组元素时:改变第二个元素的值为100,arr[1]=100;当使用[]的方式引用数组元素时,在编译器中都会转换成指针的形式操作,arr+1和&arr[1]是一样的,都表示第二个元素的首地址。
- 作为函数参数:不管以指针的形式还是数组名的形式作为函数的参数时都会被转换成指针。
void array_to_pointer(int *ia){……} //无需转换
void array_to_pointer(int ia[ ]){……} //被转换成*ia
void array_to_pointer(int ia[100 ]){……} //被转换成*ia
void array_test(int ia[100])//ia被转换成了指针不再是常指针了!!!
{
double da[10];
printf("%d", sizeof( ia ));
ia++; //没错误,按指针进行处理而非数组名
//da++; //编译错误,数组名是常指针
}
2.2指向数组的指针
指向数组的指针主要是用来对二维数组进行操作的。
理解一下 int (*p)[100]:由于括号的优先级是最高的,所以首先执行解引用,表明了p是一个指针,接下来是数组下标的引用,说明p指向的是某种类型的数组,前面的int表明p指向的这个数组的每个元素都是整数。
当对一个一位数组的数组名取&操作时,返回的是一个指向数组的指针。
例如:
int arr[100]={0};//声明一个数组
int *p=&arr;//一个指向数组的指针
int *q=arr; //arr是数组的首地址,q指向了数组的第一个元素
2.3指针数组
在声明一个指向数组的指针时千万不要丢到那个括号,如:int (*p) [100]; 如果丢掉了括号那就完全改变了意图,从而意外地声明了一个指针数组。
指针数组就是一个数组他的所有元素都是指针。
int (\*p)[100];//指向数组的指针
int* p[100];//指针数组,以p为数组名的数组中存放的都是指针元素
插播:指针常量和常量的指针...
常量指针: const char* ptr ="hello"; char const* ptr="hello";内容不能改,指针可改
指针常量: char* const ptr="hello"; 指针不能改,内容可改
指向常量的常指针:const int* const p;指针和内容都不能改
带两个const 的好区分,就是都不能改。怎么区分指针常量和常量指针呢?
1. 看const 和*的排列顺序
int const* p; const * 即常量指针
const int* p; //const * 即常量指针
int* const p; //* const 即指针常量
2. 看const离谁近,即从右向左看
int const* p; //const修饰的是*p,即*p的内容不可通过p改变,但p不是const,p可以修改,*p不可修改;
const int* p; //同上
int* const p; //const修饰的是p,p是指针,p指向的地址不能修改,p不能修改,但*p可以修改;
以上只是关于C语言指针的一些皮毛,剩下的东西后续再补充。通过今天的学习,对C语言底层的地位有了新的认识,大概是他因为跟硬件层息息相关。指针的存在让程序员可以自由地对内存进行操作,C语言赋予了程序员极大地自由的同时也带来了极大的隐患。