地址、指针、结构体、链表:一步一解,由浅入深

一、变量声明、定义与调用

extern int a, b, c;      //声明
int a, b, c;             //定义
a = 0;  b = 1;  c = 1;   //赋值
a = b + c;               //调用

二、&和*、地址和指针

1、存储空间都有相对应的地址,固定的地址对应着固定的存储空间。地址是一串数字,如0xc0200142(其字节长度与环境有关),访问该地址对应的存储空间里存的内容: *0xc0200142
2、&是 取 某内容所在存储空间的 地址
3、*是 指向 某地址对应的存储空间 里的内容
4、指针的类型 表示该指针对应的存储空间里存的内容 的类型
extern int *p;   //声明指针 p
int *p = NULL;   //定义指针p  指向空
p = &a;          //赋值指针  取a所在存储空间的地址 赋值给p

if (p != NULL) //保险起见,是否成功
{
	b = *p;      //调用(使用)指针:取 地址p对应的存储空间里的内容 赋值给b, 此操作与b=a结果相同
//使用指针的作用体现在,当某内容占存储空间比较大时,调用其指针占用空间比较小
//比如某结构体占用空间50个字节,调用其指针占用4个字节
}

三、结构体和结构体指针

例一
1、声明一个结构体类型student
2、定义一个student类型的结构体stu_1
3、定义一个student类型的结构体指针p
4、取stu_1的地址赋值给p
5、给结构体stu_1的元素赋值
6、调用结构体本身 访问结构体的成员
7、通过结构体地址 访问结构体的成员
8、通过结构体指针 访问结构体的成员
#include <string.h>
#include <stdio.h>
#include <stdlib.b>
 
struct student //1、声明一个 结构体类型 student 
{
    long num;
    char name[20];
    char sex;
    float score;
};
 
void main()
{
    struct student stu_1; //2、定义一个 student结构类型的 结构体stu_1
    struct student *p;    //3、定义一个 student结构类型的 指针p
    p = &stu_1;           //4、取 stu_1的地址 赋值给p ,这时*p就指向stu_1了
    
    //5、给 结构体stu_1的成员赋值
    stu_1.num = 89101;    
    strcpy(stu_1.name, "Li Lin");
    stu_1.sex = 'M';
    stu_1.score = 89.5;
    
    //6、调用结构体本身 访问结构体的成员
    printf("NO. :%ld\nname: %s\nsex:%c\nscore:%f\n", stu_1.num, stu_1.name, stu_1.sex, stu_1.score); 

    //7、通过结构体地址 访问结构体的成员
    printf("NO. :%ld\nname: %s\nsex:%c\nscore:%f\n", p->num, p->.name, p->.sex, p->.score);     

    //8、通过结构体指针 访问结构体的成员
    printf("NO. :%ld\nname: %s\nsex:%c\nscore:%f\n", (*p).num, (*p).name, (*p).sex, (*p).score); 
    system("pause");
}

例二
#include <stdio.h>
#include <string.h>
 
struct Books  //声明一个 结构体类型 Books  
{
   char  title[50];
   char  author[50];
   char  subject[50];
   int   book_id;
}Book1;

void printBook( struct Books *book )
{
   printf( "书标题 : %s\n", (*book ).title);
   printf( "书作者 : %s\n", book ->author);
}

 
int main( )
{
	/* Book1 元素赋值 */
	strcpy( Book1.title, "C Programming");
	strcpy( Book1.author, "Nuha Ali");  
	strcpy( Book1.subject, "C Programming Tutorial");
	Book1.book_id = 6495407;

	/* 通过传 Book1 的地址来输出 Book1 信息 */
	printBook( &Book1 );
 
   return 0;
}

四、套娃与链表

1、typedef关键字

#define与 typedef
typedef仅限于为类型定义符号名称,#define不仅可以为类型定义别名,也能为数值定义别名。
typedef是由编译器执行解释的,#define语句是由预编译器进行处理的。
简而言之,#define 只是字面上的替换,由预处理器执行,#define A B 相当于打开编辑器的替换功能,把所有的B替换成A。

typedef unsigned char BYTE;
BYTE  b1, b2;  //unsigned char b1, b2;
2、套娃
1、typedef … Ex2:Ex2标识符 表示一个结构体类型
2、struct EX :定义一个 结构体类型EX ,注意成员d是 Ex结构类型的指针
3、typedef … EX:Ex标识符 表示Ex结构体类型
4、定义一个 Ex结构类型的结构体变量x,并给成员赋值
5、定义一个 Ex结构类型的 结构体指针px,并赋值,指向结构体x
6、定义一个 Ex结构类型的 结构体y
7、给 x的成员d(EX结构类型的结构体指针)赋值,指向结构体y
typedef struct{
	int a;
	short b
}Ex2;  // 1、typedef ... Ex2
       // Ex2标识符 表示 这个结构体类型
       
typedef struct EX{  // 2、struct EX :定义一个 结构体类型 EX 
	int a;
	char b[3];
	Ex2 c;
	struct EX *d;  //注意! 成员d是 Ex结构类型的 指针
}EX;  //3、typedef ... EX
      // Ex标识符 表示 Ex结构体类型
      
Ex x={10, "hi", {5, {-1,25} }, 0};  //4、定义一个 Ex结构类型的 结构体变量x 并给成员赋值
Ex *px = &x;  //5、定义一个 Ex结构类型的 结构体指针px,并赋值 指向结构体x
Ex y;  //6、定义一个 Ex结构类型的 结构体y
x.d = &y;  //7、给 x的成员d(EX结构类型的指针)赋值,指向结构体y
3、套娃与访问成员:

px->d->a
px->d->b
px->d->c
px->d->c.a
px->d->c.b[1]
最后一个表达式的右值如下图中的小圆圈:
在这里插入图片描述

4、简单链表
#include<stdio.h>
#define NULL 0
struct student{
	long num;
	float score;
	struct student *next;
};

void main()
{
	struct student a,b,c;
	struct student *head,*p;
	a.num=10101;a.score=89.5;
	b.num=10103;b.score=90;
	c.num=10107;c.score=85;
	head=&a;
	a.next=&b;
	b.next=&c;
	c.next=NULL;
	p=head;
	do
		{
			printf("%ld %5.lf\n",p->num,p->score);
			p=p->next;
		}
	while(p!=NULL);
}
运行结果为:

101010 89.5
10103 90.0
10107 85.0

5、创建(动态)链表
所谓动态链表是可以指定链表长度,动态分配存储空间。
1、创建三个LinkList类型的指针
2、malloc在内存动态存储区分配一个LNode类型、长度为sizeof(LNode)的连续空间(假设该空间叫做"盒子1"),然后返回该空间的首地址
3、将首地址赋值给L,此时L指向"盒子1"。
4、在这个函数中,L是整个链表的头指针,从开始被赋值就没再被改变过
5、L赋值给q,此时q和L都指向"盒子1"
6、创建新空间,p指向该空间。(假设该空间叫做"盒子2")
7、q->next 指向"盒子2",即 “盒子1->next” 指向 “盒子2”
8、p赋值给q,此时q和p都指向"盒子2"
9、在整个过程中,p负责开拓新结点(对应第6步),q负责连接新节点(第7步)然后指向当前尾节点(第8步)
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
typedef struct LNode{
    int          data;
    struct LNode *next;
}LNode,*LinkList;  
//注意!LinkList是 被关键字typedef修饰的标识符,表示LNode结构类型的指针

LinkList CreateList(int n);
void print(LinkList h);
int main()
{
    LinkList Head=NULL;
    int n;
    
    scanf("%d",&n);
    Head=CreateList(n);
    
    printf("刚刚建立的各个链表元素的值为:\n");
    print(Head);
    
    printf("\n\n");
    system("pause");
    return 0;
}
LinkList CreateList(int n)
{
    LinkList L,p,q;  //1、创建三个LinkList类型的指针
    int i;
    
    /*2、malloc在内存动态存储区分配一个LNode类型、长度为sizeof(LNode)的连续空间
    (假设该空间叫做"盒子1"),然后返回该空间的首地址*/
    L=(LNode*)malloc(sizeof(LNode));  
    //3、将首地址赋值给L,此时L指向"盒子1"。
    //4、在这个函数中,L是整个链表的头指针,从开始被赋值就没再被改变过
    
    if(!L)return 0;
    L->next=NULL;
    q=L;  //5、L赋值给q,此时q和L都指向"盒子1"
    for(i=1;i<=n;i++)
    {
        //6、创建新空间,p指向该空间。(假设该空间叫做"盒子2")
        p=(LinkList)malloc(sizeof(LNode));  
        
        printf("请输入第%d个元素的值:",i);
        scanf("%d",&(p->data));
        p->next=NULL;
        q->next=p;  //7、q->next 指向"盒子2",即"盒子1->next"指向"盒子2"
        q=p;  //8、p赋值给q,此时q和p都指向"盒子2"
//9、在整个过程中,p负责开拓新结点(对应第6步),q负责连接新节点(第7步)然后指向当前尾节点(第8步)
    }
    return L; 
}
void print(LinkList h)
{
    LinkList p=h->next;
    while(p!=NULL){
        printf("%d ",p->data);
        p=p->next;
    }
}

五、作为函数参数及优化

1、整个结构体作为传参

在这里插入图片描述
在这里插入图片描述
总价=数量*单价;
在这里插入图片描述

结构的一份拷贝作为参数传递给函数并被修改。然后一份修改后的结构拷贝从函数返回,所以这个结构被复制了两次。
例如将a传入函数过程,是将a复制一份赋值给函数的(局部)临时变量(参数),在函数执行过程中都基于此临时变量。在函数结束时会释放该临时变量丢失其数据,可以在函数结束前返回该临时变量,并用变量接收该临时变量。
如果传参所占存储空间大,那么使用指针代替是个好办法。
注:“结构体指针类型” 的 “类型”两字,表示该地址“所存储内容的类型"

假如磁盘控制器其中一个寄存器是如下定义的:
在这里插入图片描述
假如磁盘寄存器实在内存地址0xc0200142进行访问的,我们可以声明下面的指针变量

#define DISK_REGISTER  ((struct DISK_REGISTER_FORMAT *)0xc0200142)

/*
** 告诉控制器从哪个扇区哪个磁道开始读取。
*/
DISK_REGISTER->sector = new_sector;
DISK_REGISTER->track = new_track;
DISK_REGISTER->command = READ;

/*
** 等待,直到操作完成(ready变量变成真)。
*/
while( !DISK_REGISTER->ready );
/*
** 检查错误
*/
if( DISK_REGISTER->error_occurred ) {
switch( DISK_REGISTER->error_code ) {
...

在这里插入图片描述

2、结构体地址作为传参

在这里插入图片描述

六、链表插入——指针

1、包含特殊情况

在这里插入图片描述
在这里插入图片描述

2、优化插入函数

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、一些事

在这里插入图片描述在这里插入图片描述

七、链表倒置——指针

1、迭代法

在遍历链表时,将当前节点的 next 指针改为指向前一个节点。由于节点没有引用其前一个节点,因此必须事先存储其前一个节点。在更改引用之前,还需要存储后一个节点。最后返回新的头引用。

也就是说,需要三个结构体指针,用于保存当前节点(curr)及其前节点(prev)、后节点(next)。
struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* prev = NULL;
    struct ListNode* curr = head;
    while (curr) {
        struct ListNode* next = curr->next;
        curr->next = prev;
        prev = curr;
        curr = next;
    }
    return prev;
}
2、递归法

递归版本稍微复杂一些,其关键在于反向工作。假设链表的其余部分已经被反转,现在应该如何反转它前面的部分?

struct ListNode* reverseList(struct ListNode* head) {
    if (head == NULL || head->next == NULL) {
        return head;
    }
    struct ListNode* newHead = reverseList(head->next);
    head->next->next = head;
    head->next = NULL;
    return newHead;
}
注:本文章内容是本人一些理解,资料来自《C和指针》及网络
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值