学习目标指针
指针定义指针的值----或者叫指针所指向的内存区或地址&和*指针表达式 结构体指针宏定义
注意 Typedef
注意
指针 指针定义int p; – 这是一个普通的整型变量
int *p; – 首先从 p 处开始,先与 * 结合,所以说明 p 是一个指针, 然后再与 int 结合, 说明指针所指向的内容的类型为 int 型。所以 p 是一个返回整型数据的指针。
int p[3]– 首先从 p 处开始,先与 [] 结合,说明 p 是一个数组, 然后与 int 结合, 说明数组里的元素是整型的, 所以 p 是一个由整型数据组成的数组。
int *p[3];– 首先从 p 处开始, 先与 [] 结合,因为其优先级比 * 高,所以 p 是一个数组, 然后再与 * 结合, 说明数组里的元素是指针类型, 然后再与 int 结合, 说明指针所指向的内容的类型是整型的, 所以 p 是一个由返回整型数据的指针所组成的数组。
int (*p)[3]; – 首先从 p 处开始, 先与 * 结合,说明 p 是一个指针然后再与 [] 结合(与"()"这步可以忽略,只是为了改变优先级), 说明指针所指向的内容是一个数组, 然后再与int 结合, 说明数组里的元素是整型的。所以 p 是一个指向由整型数据组成的数组的指针。
int **p;– 首先从 p 开始, 先与 * 结合, 说是 p 是一个指针, 然后再与 * 结合, 说明指针所指向的元素是指针, 然后再与 int 结合, 说明该指针所指向的元素是整型数据。由于二级指针以及更高级的指针极少用在复杂的类型中, 所以后面更复杂的类型我们就不考虑多级指针了, 最多只考虑一级指针。
int p(int); – 从 p 处起,先与 () 结合, 说明 p 是一个函数, 然后进入 () 里分析, 说明该函数有一个整型变量的参数, 然后再与外面的 int 结合, 说明函数的返回值是一个整型数据。
int (*p)(int);– 从 p 处开始, 先与指针结合, 说明 p 是一个指针, 然后与()结合, 说明指针指向的是一个函数, 然后再与()里的 int 结合, 说明函数有一个int 型的参数, 再与最外层的 int 结合, 说明函数的返回类型是整型, 所以 p 是一个指向有一个整型参数且返回类型为整型的函数的指针。
int *(*p(int))[3]; – 可以先跳过, 不看这个类型, 过于复杂从 p 开始,先与 () 结合, 说明 p 是一个函数, 然后进入 () 里面, 与 int 结合, 说明函数有一个整型变量参数, 然后再与外面的 * 结合, 说明函数返回的是一个指针, 然后到最外面一层, 先与[]结合, 说明返回的指针指向的是一个数组, 然后再与 * 结合, 说明数组里的元素是指针, 然后再与 int 结合, 说明指针指向的内容是整型数据。所以 p 是一个参数为一个整数且返回一个指向由整型指针变量组成的数组的指针变量的函数。
指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。
在32 位程序里,所有类型的指针的值都是一个32 位整数,因为32 位程序里内存地址全都是32 位长。
指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为sizeof(指针所指向的类型)的一片内存区。
这里&是取地址运算符,*是间接运算符。
&a的运算结果是一个指针,指针的类型是 a 的类型加个 *,指针所指向的类型是 a 的类型,指针所指向的地址嘛,那就是 a 的地址。
*p的运算结果就五花八门了。总之 *p 的结果是p 所指向的东西,这个东西有这些特点:它的类型是 p 指向的类型,它所占用的地址是 p 所指向的地址。
指针表达式int a,b; int array[10]; int *pa; pa=&a; //&a 是一个指针表达式。 Int **ptr=&pa; //&pa 也是一个指针表达式。 *ptr=&b; //*ptr 和&b 都是指针表达式。 pa=array; pa++; //这也是指针表达式。 char *arr[20]; char **parr=arr; //如果把arr 看作指针的话,arr 也是指针表达式 char *str; str=*parr; //*parr 是指针表达式 str=*(parr+1); //*(parr+1)是指针表达式 str=*(parr+2); //*(parr+2)是指针表达式结构体指针
首先声明一个结构体,这里用结构体来存储书本的信息,包括书本的标题、作者、价格、出版日期。其中出版日期也是一个结构体,这里声明出版日期时使用的是匿名结构声明,没有具体的结构名称。它嵌套在书本结构体中。接下来初始化结构体。
struct book books=
{
"语文","张三",19.8,{2021,10,1}
};
struct book *bks;
bks = &books;
定义了一个结构体变量books,在定义的时候直接初始化。接着定义了结构体指针,将books的地址赋值给指针。这里要注意一下,结构体的变量名并不是结构体的指针,所以在给指针赋值的时候,必须要使用取值运算符&来获取结构体变量的指针。接下来就可以使用指针来访问结构体的成员了。
printf("%s %s %f %d-%d-%drn",books.title,books.author,books.value,books.year,books.month,books.day);
printf("%s %s %f %d-%d-%drn",(*bks).title,(*bks).author,(*bks).value,(*bks).year,(*bks).month,(*bks).day);
printf("%s %s %f %d-%d-%drn",bks->title,bks->author,bks->value,bks->year,bks->month,bks->day);
第二种是通过指针来访问具体对象,由于 bks = &books ,那么 *bks = books,因为 & 和 * 是一对互逆运算符,所以可以做如下的替换:
books.title == (*bks).title
这里的 *bks 必须要加圆括号,因为 . 运算符的优先级比 * 运算符优先级高。如果不加圆括号就相当于 *(bks.title),这里一定要记得加括号。
第三种方法是用指针访问结构体的最常用方法,使用 -> 运算符,指向结构体指针的后面加 -> 运算符和变量后面加 . 运算符的是等效的。
books.title == bks->title宏定义
#define 叫做宏定义命令,它也是C语言预处理命令的一种。所谓宏定义,就是用一个标识符来表示一个字符串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串。宏定义是由源程序中的宏定义命令#define完成的,宏替换是由预处理程序完成的。
#define 宏名 字符串
#表示这是一条预处理命令,所有的预处理命令都以 # 开头。宏名是标识符的一种,命名规则和变量相同。字符串可以是数字、表达式、if 语句、函数等。
注意- 带参宏定义中,形参之间可以出现空格,但是宏名和形参列表之间不能有空格出现。在带参宏定义中,不会为形式参数分配内存,因此不必指明数据类型。而在宏调用中,实参包含了具体的数据,要用它们去替换形参,因此实参必须要指明数据类型。在宏定义中,字符串内的形参通常要用括号都括起来,包括嵌层括号,以避免出错。
主要为四种用法:
- 为基本数据类型定义新的类型名
typedef unsigned int COUNT;
- 为自定义数据类型(结构体、共用体和枚举类型)定义简洁的类型名称
typedef struct tagPoint
{
double x;
double y;
double z;
} Point;
- 为数组定义简洁的类型名称
typedef int INT_ARRAY_100[100]; INT_ARRAY_100 arr;
- 为指针定义简洁的名称
typedef char* PCHAR; PCHAR pa;注意
typedef char* PCHAR; int strcmp(const PCHAR,const PCHAR);
在上面的代码中,“const PCHAR”不相当于“const char*”。
因为typedef 是用来定义一种类型的新别名的,它不同于宏,不是简单的字符串替换。因此,“const PCHAR”中的 const 给予了整个指针本身常量性,也就是形成了常量指针“charconst(一个指向char的常量指针)”。即它实际上相当于“charconst”,而不是“const char*(指向常量 char 的指针)”。当然,要想让 const PCHAR 相当于 const char* 也很容易,如下面的代码所示:
typedef const char* PCHAR; int strcmp(PCHAR, PCHAR);



