C语言指针

邮差的信 提交于 2019-12-01 15:17:06

C语言是一款强大的语言,也是一本比较简单容易上手的编程语言,但是C语言也有重点难点,那就是指针和链表,我一直不得其门而入,现在我想记录下所有我在学习指针和链表过程中的重点。

指针的含义

相信很多在学指针的你们应该都能在网上看到了一个说法,那就是C语言的指针其实也是属于变量,只不过这是一种特殊的变量,是用来保存变量地址的变量。

指针的用处

看下面的代码例子:

#include "pch.h"
#include <stdio.h>

void change(int x, int y) {
    int temp = x;
    x = y;
    y = temp;
}

int main()
{
    int a = 3;
    int b = 6;
    change(a, b);
    printf("a=%d,b=%d\n", a, b);
    return 0;
}

a和b通过change方法交换值,这个方法貌似没什么问题,然而输出的结果却是两个值并没有交换。 Image
结果显示依然是a=3,b=6。为什么会这样呢,我是学C#出身的,更加不能理解结果会是这样,但是我忘记了,C语言是面向过程的语言,并不像C#是面向对象的语言。
C语言普通的函数参数是传值的参数,main函数中的a和b与函数change的参数x和y是不同的值,虽然a通过change函数传值,使得a和x的值相等,但是也仅仅是值相等,两个值的地址并不是同一个地址,所以,x的值修改了但最终a的值没有修改。
再看下面的例子:

#include <stdio.h>

void change2(int *x, int *y) {
    int temp = *x;
    *x = *y;
    *y = temp;
}

int main()
{
    int a = 3;
    int b = 6;
    change2(&a, &b);
    printf("a=%d,b=%d\n", a, b);
    return 0;
}

Image
将交换的方法的参数修改成指针后,a和b的值就进行了交换,这又是为什么呢?主要是因为指针变量其实是保存变量地址的变量,例子中change2函数的指针x和y,在main函数中调用,则是直接将a和b的地址传进去了,那么修改的值当然是直接修改a和b所在地址的值了,所以最终a和b的值也被修改了。

指针的声明和定义

指针声明的格式:[数据类型] *[变量名称];
如下例子:

int *p;        // 声明一个 int 类型的指针 p
int **p;       // 声明一个指针 p ,该指针指向一个 int 类型的指针
int *arr[10];  // 声明一个指针数组,该数组有10个元素,其中每个元素都是一个指向 int 类型对象的指针
int(*arr)[10]; // 声明一个数组指针,该指针指向一个 int 类型的一维数组

和普通的变量不一样,指针变量多了一个一元运算符“”,这个符号表示间接寻址或间接引用运算符。
p表示p是指向某个变量地址的指针变量,而直接用p变量则表示是这个变量的地址。

#include <stdio.h>

int main()
{
    int a = 3;
    int *p = &a;

    printf("*p=%d\n", *p);
    printf("p=%d\n", p);
    return 0;
}

Image
例子中可以看出,p表示是指向a的地址的值,p则表示a的地址。我们可以这样理解:p 是一个指针,保存着一个地址,该地址指向内存中的一个变量; p 则会访问这个地址所指向的变量。

指针的初始化

指针声明后,当然是要初始化指针,如果不初始化,那么我们并不知道我们定义的指针指向了哪个地址,这样就容易导致报错了,那么这里,我主要记录有两种指针的初始化方式。
第一种,通过指向已经初始化的变量的地址:

int a = 9;
int *p = &a;

第二种,给指针分配动态内存地址,但是分配的地址在使用完毕后需要释放,否则会浪费cpu的资源的(这里的分配malloc和free函数都在stdlid.h头文件中):

int *q = (int *)malloc(sizeof(int));
free(q);

没有初始化的指针,我们不知道它指向哪里,它有可能指向一个非法的地址,那它就变成了一个非法指针,这样就会爆内存的错误;当然,如果运气好的话,它也有可能指向了一个合法的地址,但是这样更棘手,因为是合法的地址,程序能够正常运行,但是你可能修改了一个你不知道的合法地址内的值,而你本意并不是想修改这个地址的值。
所以,声明的指针必须初始化,即使我们不初始化,也要给一个默认值0或者NULL。

int *p = NULL;
printf("*p指向的地址是%d\n", p);

设置指针是一个空指针,表示指针不指向任何地址,这样就能避免上述出现的两个问题,当然,设置成0也是一样,但是这样就需要转换数据类型,消耗性能,不建议。
下面是几种引用指针的方法:

int a[] = { 2,3,4,5,6 };
int *p;
p = a;        // 指向a数组的首地址
p = &a[0];    // 指向a数组的首地址(同p = a)
*p = a[0];    // 指向a数组的首地址(同p = a) 
结构体

从一周前学C语言到现在,我觉得结构体算是和C#里最相像的一部分了,C语言的结构体是能够声明在头文件中的,下面是定义结构体的各种方式。

struct student
{
    char name[10];
    int age;
    int weight;
    int height;
};

这种方式是声明了一个名称叫student的结构体类型,我们可以直接定义多个该类型的结构体变量,如:

struct student stu;
struct student stu1;
struct student stu2;
...

除了以上的声明方法,我们还能通过直接定义结构体变量。

struct {
    char name[10];
    int age;
    int weight;
    int height;
}student1,student2;

这里就已经定义了student1和student2两个结构体变量,这里没有定义结构体类型,所以,只能定义这两个变量而不能再定义其他类型的了。当然我们也是可以在定义变量的同时把类型也定义了。

struct student{
    char name[10];
    int age;
    int weight;
    int height;
}student1,student2;

我所知道的所有的定义方式我都记录下来了,至于怎么用,就要看开发的场景是怎么样的了。

链表

学完指针和结构体,接下来肯定要学习两者结合起来的链表,这是C语言的重点,也是难点。
其实链表就是结构体指针,但是在结构体内部又定义了本身类型的结构体指针类型。
下面是单链表的初始化:

typedef struct student {
    int stuId;
    char stuName[10];
    float stuScore;
    char stuSex[2];
    struct student *nextstu;
}student_t;

下面是双链表的初始化:

typedef struct student {
    int stuId;
    char stuName[10];
    float stuScore;
    char stuSex[2];
    struct student *prestu;
    struct student *nextstu;
}student_t;

单链表其实就是当前的结构体指针,内部也包含一个指针,指向下一个指针;而双链表其实就是当前的结构体指针,内部包含了两个指针,一个指向前一个指针,一个指向后一个指针,如图:
Image
Image
到这里,C语言基础我已经基本复习完毕了(大学的时候学过基础,但没用过,算是复习了吧),复习完当然得要动手做个小东西了,所以我模仿网上,写了一个学生信息管理系统,比较小,但是麻雀虽小,五脏俱全,下面是代码:

int selection;

typedef struct student {
    int stuId;
    char stuName[10];
    float stuScore;
    char stuSex[2];
    struct student *nextstu;
}student_t;

student_t *stu_List;

void initstulist();
void addstu();
void deletestu();
void searchstu();
void changestu();
void liststus();
#include <stdio.h>
#include <stdlib.h>
#include "CRUD.h"

void initstulist() {
    stu_List = NULL;
}

void addstu() {
    // 初始化新建节点
    student_t *stu = (student_t *)malloc(sizeof(student_t));
    stu->nextstu = NULL;    
    // 提示用户输入数据
    printf("请输入学生信息\n学号:");
    scanf("%d", stu->stuId);
    printf("姓名:");
    scanf("%s", &(stu->stuName));
    printf("总分:");
    scanf("%f", &(stu->stuScore));
    printf("性别:");
    scanf("%s", &(stu->stuSex));
    if (stu_List == NULL) {
        stu_List = stu;
    }
    else
    {
        student_t *prestu = stu_List;
        while (prestu->nextstu != NULL) {
            prestu = prestu->nextstu;       
        } ;
        prestu->nextstu = stu;
    }
    liststus();
    
    char iscontinue[2];
    printf("是否继续添加?(y/n):");
    scanf("%s", &iscontinue);
    if (iscontinue[0] == 'y') {
        addstu();
    }
}

void deletestu() {
    liststus();
    int stuId;
    printf("请在这里输入你要删除的学生序号:");
    scanf("%d", &stuId);
    student_t *prestu = stu_List;
    student_t *stu = NULL;
    if (prestu->stuId == stuId) {
        stu_List = prestu->nextstu;
        return;
    }
    stu = stu_List->nextstu;
    while (stu != NULL)
    {
        if (stu->stuId == stuId) {
            prestu->nextstu = stu->nextstu;
            break;
        }
        prestu = prestu->nextstu;
        stu = stu->nextstu;
    }
    liststus();
}

void searchstu() {
    int stuId;
    printf("请在这里输入你要查找的学生序号:");
    scanf("%d", &stuId);
    student_t *stu = stu_List;
    while (stu != NULL)
    {
        if (stu->stuId == stuId) {
            break;
        }
        stu = stu->nextstu;
    }
    if (stu == NULL) {
        printf("没有查询到对应的结果,请检查是否输入错误?");
    }
    else
    {
        printf("|    学号    |    姓名    |    总分    |    性别    |\n");
        printf("| %d | %s | %f | %s |\n", stu->stuId, stu->stuName, stu->stuScore, stu->stuSex);
    }
    getchar();
    getchar();

    char iscontinue[2];
    printf("是否继续查询?(y/n):");
    scanf("%s", &iscontinue);
    if (iscontinue[0] == 'y') {
        searchstu();
    }
}

void changestu() {
    liststus();
    student_t *newstu = (student_t *)malloc(sizeof(student_t));
    newstu->nextstu = NULL;
    // 提示用户输入数据
    printf("请输入需要更改的学生信息\n学号:");
    scanf("%d", &(newstu->stuId));
    printf("姓名:");
    scanf("%s", &(newstu->stuName));
    printf("总分:");
    scanf("%f", &(newstu->stuScore));
    printf("性别:");
    scanf("%s", &(newstu->stuSex));

    student_t *prestu = stu_List;
    student_t *stu = NULL;
    if (prestu->stuId == newstu->stuId) {
        newstu->nextstu = prestu->nextstu;
        return;
    }
    stu = stu_List->nextstu;
    while (stu != NULL)
    {
        if (stu->stuId == newstu->stuId) {
            prestu->nextstu = newstu;
            newstu->nextstu = stu->nextstu;
            break;
        }
        prestu = prestu->nextstu;
        stu = stu->nextstu;
    }
    liststus();

    char iscontinue[2];
    printf("是否继续修改?(y/n):");
    scanf("%s", &iscontinue);
    if (iscontinue[0] == 'y') {
        changestu();
    }
}

void liststus() {
    printf("|    学号    |    姓名    |    总分    |    性别    |\n");
    student_t *stu = stu_List;
    while (stu != NULL)
    {
        printf("| %d | %s | %f | %s |\n", stu->stuId, stu->stuName, stu->stuScore, stu->stuSex);
        stu = stu->nextstu;
    }
    getchar();
    getchar();
}
#include <stdio.h>
#include <stdlib.h>
#include "CRUD.h"

void viewMain() {
    while (1) {
        system("CLS");
        printf("*******************************\n");
        printf("1.增加一条学生信息。\n");
        printf("2.删除一条学生信息。\n");
        printf("3.查询一条学生信息。\n");
        printf("4.修改一条学生信息。\n");
        printf("5.列出学生信息表。\n");
        printf("*******************************\n");
        printf("请在这里输入你的选择(输入序号按回车即可):");
        scanf("%d", &selection);        
        switch (selection)
        {
            case 1: // 增
                addstu();
                break;
            case 2:// 删
                deletestu();
                break;
            case 3:// 查
                searchstu();
                break;
            case 4:// 改
                changestu();
                break;
            case 5:// 列数据
                liststus();
                break;
            default:
                break;
        }
    }
}

int main(int argc, char *argv[]) {
    initstulist();
    viewMain();
    return 0;
}

Image

在写代码的时候,我发现了几个我之前没有注意的问题:
1.在用scanf输入数据到结构体属性中时,依然需要用取地址符号;

printf("请输入需要更改的学生信息\n学号:");
scanf("%d", &(newstu->stuId));
printf("姓名:");
scanf("%s", &(newstu->stuName));
printf("总分:");
scanf("%f", &(newstu->stuScore));
printf("性别:");
scanf("%s", &(newstu->stuSex));

这让我有点措手不及,暂时没有找到好的办法,所以就直接这样写值了。
2.在我定义字符的时候,不能直接定义char类型,要定义char数组,否则会报堆栈内存错误,而且定义的char数组长度还要大于1,这让我有点摸不着头脑

char iscontinue[2];
printf("是否继续修改?(y/n):");
scanf("%s", &iscontinue);
if (iscontinue[0] == 'y') {
    changestu();
}
下面我要去网上查找解决这两个问题的方法了,如果大家有好的建议希望能指出,我一定修改!!——来自一个菜鸟的心声
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!