相关知识:
内存与地址:在计算机中,数据是存放在内存单元中的,一般把内存中的一个字节称为一个内存单元。为了更方便地访问这些内存单元,可预先给内存中的所有内存单元进行地址编号,根据地址编号,可准确找到其对应的内存单元。由于每一个地址编号均对应一个内存单元,因此可以形象地说一个地址编号就指向一个内存单元。C 语言中把地址形象地称作指针。
????????C语言中的每个变量均对应内存中的一块内存空间,而内存中每个内存单元均是有地址编号的。在 C 语言中,可以使用运算符 & 求某个变量的地址。
一、指针
1.指针的概念
指针是对地址操作的一种方法,指针变量可以保存地址值,所以又把指针变量比喻成地址箱
2.指针变量的定义及使用
2.1、定义指针:
定义格式为:类型 * 变量名;
例:
int *pa;
//定义了一个整型指针变量 pa,该指针变量只能指向基类型为 int 的整型变量,即只能保存整型变量的地址。
- *号标识该变量为指针类型,当定义多个指针变量时,在每个指针变量名前面均需要加一个 *,不能省略,否则为非指针变量。
int *pa,*pb;
//表示定义了两个指针变量 pa、pb
int *pa,pb;
//则仅有 pa 是指针变量,而 pb 是整型变量
- 可以自定义指针类型,如结构体,数组等
#include <stdio.h>
typedef struct
{
float temperature; //温度
char humidity; //湿度
char alcohol; //酒精浓度
int illumination; //光照强度
char CO; //一氧化碳浓度
int *p; // int型的指针变量
void (*fun)(); //定义一个函数指针
} sensor;//别名
sensor *Psen;//结构体指针变量Psen
int main(void)
{
Psen = &sen;//结构体指针变量赋值
Psen->humidity=20;//设置结构体成员变量
printf("humidity=%d\r\n",Psen->humidity);
}
2.1、指针数据写入
指针变量数据写入有两种方式,一种是直接在定义指针式写入,另一种是在定义后写入。例
//错误
int *p,a;
*p=&a; //指针变量是p而不是*p
//正确
int a,*p=&a,b;
p=&b;
2.3、指针移动
指针移动就是改变指针所指向的地址,例:
int a[]={1,2,3,4,5};//定义数组
int *p//定义指针
p=&a[1];//将数组第一个数的地址赋给指针
p=p+3//此时指针指向的不再是第一个数了,而是第四个数
2.4、数据调用
- ?由于是变量,故指针变量的值可以改变,也即可以改变指针变量的指向。
char c1,*pc,c2;//定义了字符变量c1、c2和字符指针变量pc
pc=&c1; //pc指向c1
pc=&c2; //pc不再指向c1,而指向c2
- 同类型的指针可以相互赋值
int a,*p1,*p2,b;//定义了两个整型变量a,b;两个整型指针变量为p1,p2
float *pf;
//正确
p1=&a; //地址箱p1中保存a的地址,即p1指向a
p2=p1; //p2也指向a,即p1和p2均指向a
//错误
pf=p1; //错误。p1,pf虽然都是指针变量,但类型不同,不能赋值
pf=&b; //错误。指针变量pf的基类型为float,b类型为int,不相同
3、指针和数组的区别
?注意:*可以用在相乘运算中,也可代表指针,所以需要辨别,例
a*b a *b a*(*b)//相乘运算
a+*b//指针
?4、几种指针的使用
4.1、函数指针
顾名思义函数指针就是将函数定义成指针,具体点就是如果在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。
函数指针的定义方式为:
函数返回值类型 (* 指针变量名) (函数参数列表);
例如:
int(*p)(int, int);
这个语句就定义了一个指向函数的指针变量 p。首先它是一个指针变量,所以要有一个“*”,即(*p);其次前面的 int 表示这个指针变量可以指向返回值类型为 int 型的函数;后面括号中的两个 int 表示这个指针变量可以指向有两个参数且都是 int 型的函数。所以合起来这个语句的意思就是:定义了一个指针变量 p,该指针变量可以指向返回值类型为 int 型,且有两个整型参数的函数。p 的类型为 int(*)(int,int)。
?例如:
#include <stdio.h>
typedef struct
{
float temperature; //温度
char humidity; //湿度
char alcohol; //酒精浓度
int illumination; //光照强度
char CO; //一氧化碳浓度
int *p; // int型的指针变量
void (*fun)(); //定义一个无返回的函数指针
} sensor;
sensor sen;
void function()
{
printf("功能函数\r\n");
}
int value = 0;
int main(void)
{
sen.fun = function;//赋值
sen.fun();//调用
}
打印结果:
?4.2、结构体指针
结构体指针,可细分为指向结构体变量的指针和指向结构体数组的指针。
- ?-指向结构体变量的指针:上一课我们通过“结构体变量名.成员名”的方式引用结构体变量中的成员,除了这种方法之外还可以使用指针。
前面讲过,&student1 表示结构体变量 student1 的首地址,即 student1 第一个项的地址。如果定义一个指针变量 p 指向这个地址的话,p 就可以指向结构体变量 student1 中的任意一个成员。例如: # include <stdio.h>
# include <string.h>
struct AGE
{
int year;
int month;
int day;
};
struct STUDENT
{
char name[20]; //姓名
int num; //学号
struct AGE birthday; //生日
float score; //分数
};
int main(void)
{
struct STUDENT student1; /*用struct STUDENT结构体类型定义结构体变量student1*/
struct STUDENT *p = NULL; /*定义一个指向struct STUDENT结构体类型的指针变量p*/
p = &student1; /*p指向结构体变量student1的首地址, 即第一个成员的地址*/
strcpy((*p).name, "小明"); //(*p).name等价于student1.name
(*p).birthday.year = 1989;
(*p).birthday.month = 3;
(*p).birthday.day = 29;
(*p).num = 1207041;
(*p).score = 100;
printf("name : %s\n", (*p).name); //(*p).name不能写成p
printf("birthday : %d-%d-%d\n", (*p).birthday.year, (*p).birthday.month, (*p).birthday.day);
printf("num : %d\n", (*p).num);
printf("score : %.1f\n", (*p).score);
return 0;
} 打印结果为:
?我们看到,用指针引用结构体变量成员的方式是:
(*指针变量名).成员名
?注意:*p 两边的括号不可省略,因为成员运算符“.”的优先级高于指针运算符“*”,所以如果 *p 两边的括号省略的话,那么 *p.num 就等价于 *(p.num) 了。
- ?指向结构体数组的指针:我们知道,结构体数组的每一个元素都是一个结构体变量。如果定义一个结构体指针变量并把结构体数组的数组名赋给这个指针变量的话,就意味着将结构体数组的第一个元素,即第一个结构体变量的地址,也即第一个结构变量中的第一个成员的地址赋给了这个指针变量。比如:
# include <stdio.h>
struct STU
{
char name[20];
int age;
char sex;
char num[20];
};
int main(void)
{
struct STU stu[5] = {{"小红", 22, 'F', "Z1207031"}, {"小明", 21, 'M', "Z1207035"}, {"小七", 23, 'F', "Z1207022"}};
struct STU *p = stu;
return 0;
}
????????此时指针变量 p 就指向了结构体数组的第一个元素,即指向 stu[0]。我们知道,当一个指针指向一个数组后,指针就可以通过移动的方式指向数组的其他元素。这个原则对结构体数组和结构体指针同样适用,所以 p+1 就指向 stu[1] 的首地址;p+2 就指向 stu[2] 的首地址……所以只要利用 for 循环,指针就能一个个地指向结构体数组元素。同样需要注意的是,要将一个结构体数组名赋给一个结构体指针变量,那么它们的结构体类型必须相同。
?二、宏定义
????????宏定义是比较常用的预处理指令,即使用“标识符”来表示“替换列表”中的内容。标识符称为宏名,在预处理过程中,预处理器会把源程序中所有宏名,替换成宏定义中替换列表中的内容。
?常见的宏定义有两种,不带参数的宏定义和带参数的宏定义。
?1.无参宏定义
无参数宏定义的格式为:
#define 标识符 替换列表
替换列表可以是数值常量、字符常量、字符串常量等,故可以把宏定义理解为使用标识符表示一常量,或称符号常量。
?例:
//可以不在行首,但只允许它前面有空格符
#define PI 3.1416 //正确,该行#前允许有空格
int a;#define N 5 //错误,该行#前不允许有空格外的其他字符
//标识符和替换列表之间不能加赋值号 =,替换列表后不能加分号
#define N =5 //虽语法正确,但预处理器会把N替换成=5
int a[N]; //错误,因为宏替换之后为 int a[=5];
//宏定义不是语句,是预处理指令,故结尾不加分号。如果不小心添加了分号,虽然有时该宏定义没问题,但在宏替换时,可能导致 C 语法错误,或得不到预期结果。
#define N 5; //虽语法正确,但会把N替换成5;
int a[N]; //语法错误,宏替换后,为int a[5;];错误
//由于宏定义仅是做简单的文本替换,故替换列表中如有表达式,必须把该表达式用括号括起来,否则可能会出现逻辑上的“错误”
#define N (3+2)
int r=N*N;
//当替换列表一行写不下时,可以使用反斜线\作为续行符延续到下一行
#define USA "The United \
States of \
America"
//如果调用 printf 函数,以串的形式输出该符号常量
printf("%s\n",USA);
2、有参宏定义
带参数的宏定义格式为:
#define 标识符(参数1,参数2,...,参数n) 替换列表
?例:
//求两个参数中最大值的带参宏定义为
#define MAX(a,b) ((a)>(b)?(a) : (b))
int main(){
int c=MAX(5,3);
//等同于
int c=((5)>(3)?(5) : (3));
}
//标识符与参数表的左括号之间不能有空格,否则预处理器会把该宏理解为普通的无参宏定义
#define MAX (a,b) ( (a) > (b) ? (a) : (b) )//错误的带参宏定义格式
//宏替换列表中每个参数及整个替换列表,都必须用一对小括号 () 括起来,否则可能会出现歧义
#include <stdio.h>
#define MUL(a,b) ((a)*(b))
int main (void)
{
int c;
c=MUL(3,(5+1));
printf("c=%d\n",c);
return 0;
}
|