C语言数组与指针一本道来

数组与指针.png

一本道来其他系列
C语言关键字
C语言注释符号一本道来
C语言编译预处理技术一本道来

指针的基础

注意本节内容可能在gcc下不能完成编译,请切换到Windows平台,使用dev-cpp或其他

  • 指针本质上也是一个变量
  • 指针要占用一定的内存空间(任何类型的指针的内存大小是一样的)
  • 指针用于保存内存地址的值

*号的意义

  • 在指针声明时,*号表示所声明的变量为指针
  • *号表示取指针指向的内存空间中的值

最佳实践(指针占用的内存空间)

#include <stdio.h>

int main()
{
    int i;
    int* pI;
    char* pC;
    float* pF;
    
    pI = &i;
    
    printf("%0X, %0X, %d\n", pI, &i, i);
    printf("%d, %d, %0X\n", sizeof(int*), sizeof(pI), &pI);
    printf("%d, %d, %0X\n", sizeof(char*), sizeof(pC), &pC);
    printf("%d, %d, %0X\n", sizeof(float*), sizeof(pF), &pF);
    
    return 0;
}

最佳示例(写地址)需要在VS或其他windows系统下测试,

#include<stdio.h>
int main()
{
    int i;
    int * p;
    p=&i;
    *((int*)0x28FEB8)=100;          
    printf("%0X\n",p);
    printf("%d\n",i);
    return 0;
 } 

传值调用与传址调用

  • 当一个函数体内部需要改变实参的值,则需要使用指针参数
  • 函数调用时实参将赋值到形参

数组的基础

  • 数组在一片连续的内存中存储元素
  • 元素的个数可以是显示的也可以是隐式的

例子

a[5]={1,2}

其中其他的都用0来填充,所以数组最后为

a[5]={1,2,0,0,0}

数组地址与数组名

  • 数组名代表数组首元素的地址
  • 数组的地址需要取地址才可以得到
  • 数组首元素的地址值与数组的地址值相同
  • 数组收元素的地址与数组的地址是两个不同的概念

数组名的盲点

  • 数组名可以看作一个常量指针(指针所指向的内容不能改变)
  • 数组“指向” 的是内存中数组首元素的起始位置
  • 在表达式中数组名只能作为右值使用
  • 只有在下列场合中数组名不能看作常量指针
    • 数组名作为sizeof操作符的参数
    • 数组名作为& 运算符的参数

最佳错误

新建一个.c文件

char * p="hello world";

再建一个.c文件

#include<stdio.h>
extern char p[];
void main()
{
    printf("%s\n",p);
}

思路很简单,就是输出hello world,然而输出的结果是错误的,原因就是上面列出来的几点中的一点。
修改

Linux


#include<stdio.h>
extern char p[];
void main()
{
        printf("%s\n",(char *)*(unsigned int *)p);
}

Windows平台


#include<stdio.h>
extern char p[];
void main()
{
        printf("%s\n",(char *)*(unsigned int *)p);
}

虽然会有warning但是程序运行是正确的

C语言中的字符串

  • 从概念上讲,C语言中没有字符串数据类型
  • 在C语言中使用字符数组来模拟字符串
  • C语言中的字符串是以'\0'结束的字符数组
  • C语言中的字符串可以分配于栈空间,堆空间或者只读存储区(不能被改变)
char* s1="Hello World1";(在只读存储区)

字符串的长度

  • 字符串的长度就是字符串说包含字符的个数
  • C语言中的字符串的长度值得是第一个'\0'字符串出现的字符个数
  • C语言中通过'\0'结束来确定字符串的长度

标准库,用来解决字符长度

strlen()

彩蛋(一行程序实现strlen)

int strlen(const char * p)
{
    return (assert(s),(* p? strlen(p+1)+1:0));
 
}

不受限制的字符串函数

  • 不受限制的字符串函数是通过寻找字符串得结束符'\0'来判断长度
    • 字符串复制函数: char* strcpy(char* dst,const char* src)
    • 字符串连接:char* strcat(char* dst,const char* src)
    • 字符串比较函数:int strcmp(const char* s1,const char* s2)

注意事项

  • 不受限制的字符串函数都是以'\0'作为结束标记来进行的,因此输入参数必须包含'\0'
  • strcat和strcpy必须保证目标字符数组的剩余空间足以保存整个源字符串
  • strcmp以0值表示两个字符串相同
    • 第一个字符串大于第二个字符串的时候返回值大于0
    • 第一个字符串小于第二个字符串的时候返回值小于0
    • strcmp不会修改参数值,但依然以'\0'作为结束符号

彩蛋(实现strcpy)

char* strcpy(char* dst,const char* src)
{
    char* ret=dst;
    assert(dst && src);
    while((*dst++=*src++)!='\0');//指针循环效率高
    return ret;
}

长度受限制的字符串函数

  • 长度受限的字符串函数接收一个显示的长度参数用于限定操作的字符串
    • 字符串复制:char* strncpy
    • 字符串连接
    • 字符串比较

指针数组和数组指针分析

数组类型

  • C语言中的数组有自己特定的类型
  • 数组的类型由元素类型数组大小共同决定
    • int array[5]的类型为int[5]

定义数组类型

  • C语言中通过typedef为数组类型重命名
    • typedef type(name)[size];
  • 数组类型(重命名了一种一个数组类型):
    • typedef int(AINT5)[5];
    • typedef float(AFLOAT10)[10];
  • 数组定义:
    • AINT5 i
    • AFLOAT10 f

数组指针

  • 数组指针用于指向一个数组
  • 数组名是数组首元素的起始地址,但并不是数组的起始地址
  • 通过将取地址符号&作用于数组名可以得到数组的起始地址
  • 可通过数组类型1定义数组指针:ArrayType* pointer;
  • 也可以直接定义:type (*pointer)[n];(用于定义数组指针)
    • pointer为数组指针的变量名
    • type为指向的数组的类型
    • n为指向的数组的大小

最佳示例

#include <stdio.h>

typedef int(AINT5)[5];
typedef float(AFLOAT10)[10];
typedef char(ACHAR9)[9];

int main()
{
    AINT5 a1;
    float fArray[10];
    AFLOAT10* pf = &fArray;
    ACHAR9 cArray;
    char(*pc)[9] = &cArray;
    char(*pcw)[4] = cArray;
    
    int i = 0;
    
    printf("%d, %d\n", sizeof(AINT5), sizeof(a1));
    
    for(i=0; i<10; i++)
    {
        (*pf)[i] = i;
    }
    
    for(i=0; i<10; i++)
    {
        printf("%f\n", fArray[i]);
    }
    
    printf("%0X, %0X, %0X\n", &cArray, pc+1, pcw+1);
    
    //pc+1指向整个数组的后一个数组
}

指针数组

  • 指针数组是一个很普通的数组
  • 指针数组中每个元素为一个指针
  • 数组指针的定义:type* pArray[n];
    • type*为数组中每个元素的类型
    • pArray为数组名
    • n为数组大小
#include <stdio.h>
#include <string.h>

int lookup_keyword(const char* key, const char* table[], const int size)//第二个是指针数组
{
    int ret = -1;
    
    int i = 0;
    
    for(i=0; i<size; i++)
    {
        if( strcmp(key, table[i]) == 0 )
        {
            ret = i;
            break;
        }
    }
    
    return ret;
}

#define DIM(a) (sizeof(a)/sizeof(*a))

int main()
{//指针数组
    const char* keyword[] = {
            "do",
            "for",
            "if",
            "register",
            "return",
            "switch",
            "while",
            "case",
            "static"
    };
    
    printf("%d\n", lookup_keyword("return", keyword, DIM(keyword)));
    printf("%d\n", lookup_keyword("main", keyword, DIM(keyword)));
}

main函数的参数

  • main函数可以理解为操作系统调用的函数
  • 在执行程序的时候可以向main函数传递参数

main函数的四种参数类型

int main()
int main(int argc)
int main(int argc,char *argv[])
int main(int argc,char *argv[],char *env[])

小结

  • 数组指针本质上是一个指针
  • 数组指针指向的值是数组的1地址
  • 指针数组本质上是一个数组
  • 指针数组中的每个元素的类型是指针

多维数组和多维指针

  • 指针变量在内存中会占用一定的空间
  • 可以定义指针来保存指针变量得地址值
int main()
{
    int a=0;
    int* p=NULL;
    int** pp=NULL;
    pp=&p;
    *pp=&a;
    return 0;
}

指向指针的指针

  • 为什么需要指向指针的指针?
    • 指针在本质上也是一个变量
    • 对于指针来讲也有传值调用与传址调用

最佳示例(动态内存大小更变)

#include<stdio.h>
#include<malloc.h>
int rest(char** p,int size,int new_size)
{
    int ret=1;
    int len=0;
    int i=0;
    char* mid=NULL;
    char* pt=NULL;
    char* pp=*p;
    if((p!=NULL)&&(new_size>0))
    {
        mid=(char*)malloc(3);
        pt=mid;
        len=(size<new_size)?size:new_size;
        for(i=0;i<len;i++)
        {
            *pt++=*pp++;
        }
        free(*p);
        *p=pt;
        
    }else{
        ret=0;
    }
    return ret;
}
void main()
{
    char *p=(char*)malloc(5);
    printf("%0X\n",p);
    if(rest(&p,5,3))
    {
        printf("%0X\n",p);
    }
}

二维数组与数组指针

  • 二维数组在内存中以一维的方式排布
  • 二维数组中的第一维是一维数组
  • 二维数组中的第二维才是具体的值
  • 二维数组的数组名可以看作常量指针
二维数组.png
#include<stdio.h>
#include<malloc.h>
 void printArray(int a[],int size)
 {
    int i=0;
    printf("printfArray:%d\n",sizeof(a));
    for(i=0;i<size;i++)
    {
        printf("%d\n",a[i]);
     }
 }
 int main()
 {
    int a[3][3]={{0,1,2},{3,4,5},{6,7,8}};
    int *p=&a[0][0];
    printArray(p,9);
    return 0;
    
 }

数组名

  • 一维数组名代表数组收元素的地址
  • 二维数组同样代表数组首元素的地址
#include<stdio.h>
int main()
{
    int a[5][5];
    int (*p) [4];
    p=a;
    printf("%d\n",&p[4][2]-&a[4][2]);
}

答案为-4,因为(*p)一次跨越4个,而a一次跨越5个

数组的遍历

int a[3][3]={{}};
for(i=0;i<3;i++)
for(j=0;j<3;j++)
*(*(a+i)+j)

动态分配二维数组

原理大家很聪明就不解释
二维数组动态分配.png
#include<stdio.h>
#include<malloc.h>
int** malloc2d(int row,int col)
{
    int** ret=(int**)malloc(sizeof(int*)*row);   
    int* p=(int*)malloc(sizeof(int)*row*col);
    int i=0;
    if(ret&&p)
    {
        for(i=0;i<row;i++)
        {
            ret[i]=p+col*i;
        }
    }else{
        free(ret);
        free(p);
        ret=NULL;
        p=NULL;

    }
    return ret;
}
void del_Array(int** a)
{
    free(a);
}
void main()
{
    int i=0,j=0;
    int row=0,col=0;
    printf("please input the row\n");
    scanf("%d",&row);
    printf("please input the col\n");
    scanf("%d",&col);   
    int** p=malloc2d(row,col);
    for(i=0;i<row;i++)
    {
        for(j=0;j<col;j++)
        {
            p[i][j]=i+j;
        }
    }
    for(i=0;i<row;i++)
    {
        for(j=0;j<col;j++)
        {
            printf("%d ",p[i][j]);
        }
        printf("\n");     
    }

}

数组参数和指针参数分析

注意C语言的编译器会让(不论是一维数组还是二维数组)数组参数退化为指针

为什么退化

  • C语言中只会以值拷贝的方式传递参数
  • 当向函数传递数组时
    • 将整个函数拷贝一份传入函数(不可取)
    • 将数组名看作常量指针传数组首元素地址

二维数组参数

  • 二维数组参数同样存在退化问题
    • 二维数组可以看作是一维数组
    • 二维数组中的每一个元素是一维数组
  • 二维数组参数中第一维的参数可以省略(退化过程)
void f(int a[10])->void f(int a[])->void f(int *a)
void g(int a[3][3])->void g(int a[][3])->void g(int (*a)[3])

注意事项

  • C语言中无法向一个函数传递任意的多维数组(针对二维以上)
  • 为了提供正确的指针运算,必须提供除一维之外的所有维的长度
  • 限制
    • 一维数组-必须提供结束的标志
    • 二维数组-不能直接传递给函数
    • 多维-无法使用
#include<stdio.h>
#include<malloc.h>
void access(int a[][3],int row)
{
    int col=sizeof(*a)/sizeof(int);//去推导出列的数量
    int i=0,j=0;
    for(i=0;i<row;i++)
    {
        for(j=0;j<col;j++)
        {
            printf("%d ",a[i][j]);
        }
        printf("\n");
    }
    
}
void main()
{
    int a[3][3]={
    {1,2,3},
    {4,5,6},
    {7,8,9}
    };
    access(a,3);
}

函数与指针分析

函数

函数类型

  • C语言中的函数有自己特定的类型
  • 函数的类型由返回值,参数类型和参数个数共同决定的
  • C语言中通过typedef为函数类型重命名
    • typedef type name(parameter list)
    • typedef int f(int,int);

函数指针

  • 函数指针用于指向一个函数
  • 函数名是执行函数体的入口地址(类似于数组名字)
  • 可通过函数类型定义函数指针:FuncType* pointer;
  • 也可以直接定义:type (*pointer)(parameter list);
    • pointer为函数指针变量名
    • type为指向函数的返回值
    • 参数类型的列表

函数指针的本质与使用

回调函数

  • 回调函数是利用函数指针实现的一种调用机制
  • 回调机制的原理
    • 调用者不知道具体事件发生的时候需要调用的具体函数
    • 被调用函数不知道何时被调用,只知道被调用后需要完成的任务
    • 当具体事件发生时,调用者通过函数指针调用具体函数
  • 回调机制的将调用者和被调用的函数分开,两者互相不依赖

最佳示例C语言回调函数

#include <stdio.h>

typedef int(FUNC)(int);

int test(int i)
{
    return i * i;
}

void f()
{
    printf("Call f()...\n");
}

int main()
{
    FUNC* pt = test;
    
    //void(*pf)() = &f;//老方法
    
    //pf();
    //(*pf)();//老方法
    
    printf("Function pointer call: %d\n", pt(2));
}

进阶回调

#include <stdio.h>

typedef int(*FUNCTION)(int);

int g(int n, FUNCTION f)
{
    int i = 0;
    int ret = 0;
    
    for(i=1; i<=n; i++)
    {
        ret += i*f(i);
    }
    
    return ret;
}

int f1(int x)
{
    return x + 1;
}

int f2(int x)
{
    return 2*x - 1;
}

int f3(int x)
{
    return -x;
}

int main()
{
    printf("x * f1(x): %d\n", g(3, f1));//注册
    printf("x * f2(x): %d\n", g(3, f2));
    printf("x * f3(x): %d\n", g(3, f3));
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; ...
    朱森阅读 3,422评论 3 44
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy阅读 9,504评论 1 51
  • 版权声明:本文为 gfson 原创文章,转载请注明出处。注:作者水平有限,文中如有不恰当之处,请予以指正,万分感谢...
    gfson阅读 2,897评论 0 6
  • 平时为了提高开发效率,会使用一些Xcode插件,例如VVDocuments等,每次随着Xcode更新,可能就用不了...
    ShenYj阅读 348评论 0 2
  • 看到情侣之间洋溢的微笑偶尔会有羡慕,感觉他们是那么的幸福,看到情侣之间的不和,又会感觉庆幸。同时也感觉有趣的灵魂在...
    珂求不可及阅读 292评论 0 3