C语言常见问题汇总——指针,数组等


灵魂拷问:你分得清数组指针,指针数组,和指向指针的指针吗???

关键词:指针,数组

如果你对指针的了解懵懵懂懂,比如&,*到底是什么,怎么用,指针怎么对一维,二维数组来访问并且进行操作,这篇文章给你答案。


一、指针是什么?

指针也就是内存地址,指针变量是用来存放内存地址的变量,在同一CPU构架下,不同类型的指针变量所占用的存储单元长度是相同的,而存放数据的变量因数据的类型不同,所占用的存储空间长度也不同。有了指针以后,不仅可以对数据本身,也可以对存储数据的变量地址进行操作。

将百度的话翻译一下(说人话):
(1)指针大小会变吗?
你的计算机如果是32位的,不管是

    int *a;
    char *b;
    double *c;

它们是32/8=4Byte【一个字节(Byte)占8位(bit)】
例如:我的计算机是64位的,自己可以使用函数sizeof()来查看其大小

    int *a;
    char *b;
    double *c;

    cout<<sizeof(int *)<<"\n";
    cout<<sizeof(b)<<"\n";

在这里插入图片描述
(2)他和老搭档&怎么用?
在这里插入图片描述

  • 直接存取时,例如
int a=1;

此时a的值为1,就对a直接进行操作就可。

  • 间接存取时

要知道: &在C语言中表示取地址符。

int a;
scanf("%d",&a);

就是对取到变量a的地址后,才能将值输入进去。
基于取地址的思想,C语言提供了指针
要知道:* 在C语言中有两个用法
①命名或者作为子函数形参时,表示用于指向地址

int *a;//*仅仅表示标识符的作用,说明a为指针变量,用于指向地址

int b=5;
void  BTLC(int *c)
main(){
BTLC(b)//这是经典的错误,怎么将指针指向变量名呢?
正确写法:BTLC(&b)
}

②在函数内时,表示四则运算或者表示取到指针所指的地址的存储的值

1*2=3//表示算数运算

void  BTLC(int *c){
*c++//典型错误,++的运算优先集要比取值*的优先级高,
//所以这表示先对地址+1,再取值,这时谁也不知道它指的是什么。。。
正确写法:(*c)++//将指向的值++

当然指针也是变量,它也有地址
int *p;
int **q;
q=&p//q来存储p的地址
}


二、使用

1.和一维数组结合

【注意】

int a[5]={1};
int *p;
p=a;//指针可以直接访问数组的首地址。

*(p+i)=p[i]//这俩操作等价,在一维里面表示取值

代码如下(示例):

#include <iostream>
using namespace std;
#define Maxsize 5
//对数组累加,*(p+i)
void add_1(int *p){
    int sum=0;
    for (int i = 0; i < Maxsize; ++i) {
        sum+=1;
        *(p+i)=sum;
    }
}

//对数组累加, p[i]
void add_2(int *p){
    int sum=0;
    for (int i = 0; i < Maxsize; ++i) {
        sum+=1;
        p[i]+=sum;//再add1的之后,a[i]再加一遍
    }
}

//输出
void Print(int a[]){
    for (int i = 0; i < Maxsize; ++i) {
        cout<<a[i]<<"\t";
    }
    cout<<endl;
}

int main(){
    int a[Maxsize]={0};
    //*(p+i)
    add_1(a);
    Print(a);
    //p[i]
    add_2(a);
    Print(a);
}

在这里插入图片描述

2.和二维数组结合

【重点来了】在这里插入图片描述
在这里插入图片描述

【注意】

int a[5][5];
int *p;
*(p+i)=p[i]//在二维里面,不表示取值,表示取每行数组的首地址
**(p+i)+j)=p[i][j]//这才表示取到二维数组的某个值

指针数组

#include <iostream>
using namespace std;
#define Maxsize 5
//对数组累加
void add_1(int a[][Maxsize]){
    int sum=0;
    int *p[Maxsize];//这是长度为Maxsize的一维数组,里面放了Maxsize个int*的指针变量
    for (int i = 0; i < Maxsize; ++i) {
        p[i]=a[i];//让每个不同的指针分别指向二维数组每行的首地址
        for (int j = 0; j < Maxsize; ++j) {
            sum+=1;
            p[i][j]=sum;
        }
    }
}

//输出
void Print(int a[][Maxsize]){
    for (int i = 0; i < Maxsize; ++i) {
        for (int j = 0; j < Maxsize; ++j) {
            cout<<a[i][j]<<"\t";
        }
        cout<<"\n";
    }
    cout<<endl;
}

int main(){
    int a[Maxsize][Maxsize]={0};
    //指针数组 int *p[Maxsize]
    add_1(a);
    Print(a);
}

在这里插入图片描述

【数组指针】

#include <iostream>
using namespace std;
#define Maxsize 5
//对数组累加
void add_2(int a[][Maxsize]){
    int sum=0;
    int (*p)[Maxsize];//这只是一个指针,指向长度为Maxsize的一维数组
    for (int i = 0; i < Maxsize; ++i) {
        p=&a[i];//让每个不同的指针分别指向二维数组每行的首地址,注意一下此时在数组指针眼里,a[i]就是一维数组
        for (int j = 0; j < Maxsize; ++j) {
            sum+=1;
            *(*(p)+j)=sum;
        }
    }
}

//输出
void Print(int a[][Maxsize]){
    for (int i = 0; i < Maxsize; ++i) {
        for (int j = 0; j < Maxsize; ++j) {
            cout<<a[i][j]<<"\t";
        }
        cout<<"\n";
    }
    cout<<endl;
}

int main(){
    int a[Maxsize][Maxsize]={0};
    //数组指针 int (*p)[Maxsize]
    add_2(a);
    Print(a);
}

在这里插入图片描述

3.递归对某一值重复操作

递归在数据结构中,尤其是树中,图中有着大量的应用
此时在调用函数中,&用于先取到一般的变量(int,char,double等)的地址,再传递给子函数的形参
代码如下(示例):

int count=0//全局变量
void Knots(BitTree T,int *c){
    if (T){
        (*c)++;
        Knots(T->lchild,&count);//都访问count的地址
        Knots(T->rchild,&count);
    } else return;
    
}
Knots(T,&count);//调用

或者
//求树的结点总数
void Knots(BitTree T,int *c){
    if (T){
        (*c)++;
        Knots(T->lchild,c);//直接接着传地址
        Knots(T->rchild,c);
    } else return;
  
}
Knots(T,&count);//调用

4.你真的理解InitList(&L)吗?

你有没有这样的疑惑:L明明是指针变量,为什么前面可以加&?为什么要加?
因为“带回来”
上面说了,如果你是对一般的变量进行先取地址,再传入到子函数时,本来就在原变量的地址上进行的操作,所以直接是对同一个变量进行着操作。
但是
我也可以传入指针啊

typedef struct Lnode {
    int data;
    struct Lnode *next;
}LNode,*LinkList;//初始化链表结点

int a[5]={0};
void PrintOut(LinkList list){//打印链表结点
    while (list->next!=NULL){
        cout<<list->next->data<<"\n";
        list=list->next;

    }
}

void InitList(LNode *list){//初始化,有问题吗?
    list=(LinkList)malloc(sizeof(Lnode));
    list->next=NULL;
    LNode *p=list;
    for (int i = 0; i < 5; ++i) {
        LNode *s;
       s=(LinkList)malloc(sizeof(LNode));
       s->data=a[i];
       s->next=NULL;
       p->next=s;
       p=s;
    }
}


int main(){
    LNode *list;
    InitList(list);//好像没什么问题,对带头结点的单链表完成了初始化
    PrintOut(list);
    return 0;
}

看看结果
在这里插入图片描述

不仅没有完成初始化,而且非正常退出???

而在初始化里面加入打印语句
在这里插入图片描述
在这里插入图片描述
说明初始化函数确实没毛病,但是还是非正常退出了
那问题在哪呢
如果我们在子函数形参前加入&
在这里插入图片描述
在这里插入图片描述
没有问题了!在主函数的头结点完成了初始化
但是

void InitList(LNode *&list){//为什么可以这么写?

原来我们在主函数调用时就传递的是指针变量,子函数的形参也是指针变量,这不就相当于,这是大家应该恍然大悟了(子函数会再声明一个新的“一模一样”的变量,需要加&才会取到原变量的地址然后进行带回),哦原来指针变量也是“变量”!

void Init(int a){
    a=1;
}

int main(){
    int a;
    Init(a);
    cout<<a;//a肯定没有变为1
}

所以
我们一般不写
在这里插入图片描述
而是在数据结构中,先定义指针变量
在这里插入图片描述
在主函数里面直接将指针变量当变量来操作

LinkList list;//类似于int a;
typedef struct Lnode {
    int data;
    struct Lnode *next;
}LNode,*LinkList;

void InitList(LinkList &list){//将头结点带回来
    list=(LinkList)malloc(sizeof(Lnode));
    list->next=NULL;
    LNode *p=list;
    for (int i = 0; i < 5; ++i) {
        LNode *s;
       s=(LinkList)malloc(sizeof(LNode));
       s->data=a[i];
       s->next=NULL;
       p->next=s;
       p=s;
    }
    PrintOut(list);
}

int main(){
    LinkList list;
    InitList(list);
    cout<<"主函数"<<endl;
    PrintOut(list);
    return 0;
}

总结

以上就是今天要讲的内容,本文简单介绍了指针以及数组的结合使用,以及指针在函数调用时的疑惑,后续将继续补充C语言中的常见问题。

不足之处,欢迎讨论。

### C语言字符串数组的输入与输出 在C语言中,由于不存在专门的字符串类型,因此通常通过字符数组来表示和处理字符串。下面展示几个具体的实例用于说明如何进行字符串数组的输入与输出。 #### 单个字符串(即字符数组)的读取与打印 对于简单的字符数组而言,可以通过`scanf()`函数配合格式化描述符`%s`来进行基本的字符串输入;而利用`printf()`则能轻松完成其显示工作[^2]: ```c #include <stdio.h> int main() { char str[100]; // 定义一个长度为99加终止符'\0'的最大容量 printf("请输入一段话:"); scanf("%s", str); // 只会读入第一个单词直到遇到空白键停止 printf("您刚才输入的是:%s\n", str); return 0; } ``` 需要注意上述方法仅适用于不含空格的一串连续字符,如果希望获取完整的句子,则需采用其他方式如`gets()`或更推荐的安全版本`fgets()`替代之[^3]。 #### 多维字符数组——多行或多条记录形式下的批量录入及展现 当涉及到多个独立但结构相似的数据项时,比如一组人的姓名列表,这时就可以考虑构建二维字符数组或是指针数组指向各自单独分配内存空间的一维字符序列了。这里给出一个多维度固定大小的情况作为示范: ```c #include <stdio.h> #define MAX_LINES 5 /* 假设最多有五位参与者 */ #define LINE_LENGTH 80 /* 每个人的名字最长不超过79个字母 */ int main(){ int i; char lines[MAX_LINES][LINE_LENGTH]; puts("依次输入五个名字:"); for (i = 0; i < MAX_LINES ; ++i){ fgets(lines[i], sizeof(lines[i]), stdin); // 移除可能存在的换行符 size_t len = strlen(lines[i]); if(len > 0 && lines[i][len-1] == '\n') lines[i][len-1]='\0'; } puts("\n大家好,这里是你们刚刚提交的信息汇总:"); for(i=0;i<MAX_LINES;++i) puts(lines[i]); return 0; } ``` 此程序片段允许用户逐次提供多达五行文字内容并最终统一呈现出来。值得注意的是,在实际应用过程中应当注意防止缓冲区溢出风险以及合理设置各项参数以适应具体需求场景变化。 #### 动态分配内存实现灵活度更高的字符串管理方案 针对未知数量或者不定长特性的字符串集合情况,借助于标准库中的动态内存分配机制能够有效提升灵活性。下面是一个创建可变规模字符串表的例子: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> void read_strings(char ***strings, int *count) { char buffer[256]; while(fgets(buffer,sizeof(buffer),stdin)){ (*count)++; // 扩展原有指针数组的空间 void* tmp = realloc(*strings,*count*sizeof(**strings)); if(!tmp){fprintf(stderr,"Memory allocation failed.\n"); exit(EXIT_FAILURE);} *strings=tmp; // 分配新成员所需空间并将数据拷贝过去 (*strings)[*count-1]=malloc(strlen(buffer)+1); strcpy((*strings)[*count-1],buffer); // 清理结尾处多余的换行符 ((*strings)[*count-1])[strcspn((*strings)[*count-1],"\n")]='\0'; if(strncmp(buffer,"end",3)==0)// 预留结束标记 break; } } int main(void){ int count=0; char **strings=NULL; printf("持续输入若干行文本直至遇见包含'end'关键字为止:\n"); read_strings(&strings,&count); printf("\n已接收%d条消息如下所示:\n",count); for(int i=0;i<count;i++) puts(strings[i]); // 记得释放资源哦~ for(int j=0;j<count;j++) free(strings[j]); free(strings); return 0; } ``` 该案例展示了怎样运用`realloc()`调整容器尺寸的同时也介绍了安全移除多余控制符号的方法论。此外还强调了良好的编程习惯—及时清理不再使用的堆上对象的重要性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

至南岸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值