数组

  • char a[n];n为整性或整性表达式时就不是一个可变长度的数组,反之则这个数组是可变长度的;(c99)
  • 字符数组可以用字符串格式一次性打印。
int main()
{
    char str[10]={'a','b','c','d','e'};
    //数组只赋值部分元素时,会将没有被赋值的元素赋值为'\0'(ASCII码为0)
    //char str[10];若全部没有赋值,则数组内元素值不确定
    printf("%s",str);
}
//输出:abcde
  • 非字符数组数组部分赋值
void main(){
    int a[10]={0},i;
    int b[10];
    //a会自动将没有赋值的部分赋值为0;b中的数据却不确定
    for(i=0;i<10;i++){
        printf("%d ",a[i]);
    }
    printf("\n");
    for(i=0;i<10;i++){
        printf("%d ",b[i]);
    }
}
//输出:0 0 0 0 0 0 0 0 0 0
//输出:-858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 -858993460 
  • 数组长度定义的两种方式

    1、手动定义长度;

int main()
{
    int num[100]={1,2,3};
    printf("%d_%d_%d_%d",num[0],num[1],num[2],num[3]);
}
//输出:1_2_3_0

    2、系统自动计算长度;

int main()
{
    int num[]={1,2,3,4};
    printf("%d_%d_%d_%d",num[0],num[1],num[2],num[3]);
}
//输出:1_2_3_4
  • 字符数组赋值时的两种定义:
int main()
{
    char a[]="asdf",b[]={"qwer"},c[]={'a','b','c','d'};
    //a、b是字符串定义赋值,c是数组定义赋值
    //字符串定义赋值时会在末尾添加'\0',所以a、b占5个字节
    //数组定义时则不会在末尾添加'\0',所以c占4个字节(并且由于后面没有'\0',所以长度不确定)
    printf("占空间:%d %d %d\n",sizeof(a),sizeof(b),sizeof(c));
    printf("a长度:%s__%d\n",a,strlen(a));
    printf("b长度:%s__%d\n",b,strlen(b));
    printf("c长度:%s__%d\n",c,strlen(c));
}
//输出:占空间:5 5 4
//      a长度:asdf__4
//      b长度:qwre__4
//      c长度:abcdasdf__8

字符串

  • C语言中没有字符串变量类型,c语言中有两种表示字符串的方法:一是字符数组,二是字符指针指向字符串常量
  1. 读写能力不同:字符数组可以通过访问和修改数组内容,字符串常量只能通过指针访问字符串常量(只能读,不能写、修改)
  2. 存储位置不同:字符数组存储在栈中,字符串常量存储在静态存储区中(栈中数据访问速度高于静态区)
char str1[]="asdfasdf";//字符数组表示的字符串
char *str2="asdfasdf";//字符指针指向字符串常量
  • 字符数组,主要有以下两种定义方法

    1、指定字符串长度(超出长度不会出错)

int main()
{
    char str[10]={"asdfqwre"};
    puts(str);
}
//输出:asdfqwer

    2、自动计算字符串长度

int main()
{
    char str[]={"asdfqwre"};
    printf("%s_%d",str,sizeof(str));
    //输出字符数组(串)的尺寸的9,可以发现系统会在末尾自动添加了'\0'
}
//输出:asdfqwer_9
  • 字符指针,这样指定的字符串相当于字符串常量,只可读,不可写
int main()
{
    char *str="asdf";
    printf("%s_%d",str,strlen(str));
    //从输出str的尺寸为8可以知道他是指针
}
//输出:asdf_8
  • 字符数组与字符指针表示的字符串不同:字符串类型不同
int main()
{
    char str1[20]="asdfqwer",*str2="asdfqwer";
    //这种定义方式,字符数组只可在定义的同时才可以使用,定义之后,不可再用这种方法赋值也验证了数组名是一个地址常量(右值)
    //而字符指针却随时可以这样定义
    str2="hahaha";
    //相当于,系统在一片内存写下一个字符串,而让字符指针指向这片地址
    //所以,字符指针表示的字符串是字符常量,只可读,不可写
    //str2[1]='x';不合法写入
    puts(str1);puts(str2);
}
//输出:asdfqwer
//      hahaha
  • 由于字符指针表示的相当于字符串常量,所以不可以对字符指针表的字符串进行写操作
int main()
{
    char str1[20],*str2;
    scanf("%s",str1);
    //scanf("%s",str2);gets(str2);赋值失败
    //scanf、gets这种赋值函数也只对字符数组合法,指针如果想使用,必须向指向字符数组,这样间接的操作数组
    puts(str1);
}
//输入:asdfqwer
//输出:asdfqwer

指针

  • 指针是一个左值,是一个变量,用于存放地址;
  • 不同类似指针占用的字节数是相同的(不同系统可能分配字节数不同),不像变量(int(4),char(1),float(4),double(8));
  • 变量取地址:&a,赋给指针;指针的值为指向的地址,若要操作地址的值需要 '*' 进行解引用:*a;
int main()
{
    char c='c',*cc=&c;
    //注:*cc=&c;指针定义的同时,可以直接指定指向位置
    //cc=&c;指针cc已经定义,后序指定指向位置时不可解引用
    //*cc=c;指针cc已经定义,指针cc指向地址单元赋值的操作
    printf("c=%c  *cc=%c\n",c,*cc);
    printf("&c=%p  cc=%p\n",&c,cc);
}
//输出:c=c  *cc=c
//      &c=0x7ffd4f3ab147  cc=&c=0x7ffd4f3ab147
  • 指向结构体的指针和指向普通变量的指针自加的细节
#include <stdio.h>
struct {int x;} b[3]={1,5,9},*q;
int main()
{
    int a[3]={1,5,9},*p;
    q=b;p=a;
    printf("(指针偏移量:%d)_%d\n",(int)(q-b),++q->x);
    //对引用值加加(先加)
    printf("(指针偏移量:%d)_%d\n\n",(int)(p-a),++*p);
    //对引用值加加(先加)
    a[0]=b[0].x=1;//还原自加值
    //输出 :(指针偏移量:0)_2
    //      (指针偏移量:0)_2
    
    q=b;p=a;
    printf("(指针偏移量:%d)_%d\n",(int)(q-b),q++->x);
    //对指针加加(后加)
    printf("(指针偏移量:%d)_%d\n\n",(int)(p-a),*++p);
    //对指针加加(先加)
    //输出 :(指针偏移量:1)_1
    //      (指针偏移量:1)_5
    
    q=b;p=a;
    printf("(指针偏移量:%d)_(加后:%d)_%d\n",(int)(q-b),b[0].x,q->x++);
    //对引用值加加(后加)
    printf("(指针偏移量:%d)_%d",(int)(p-a),*p++);
    //对指针加加(后加)【由于'*'与'++'同优先级,所以都是对指针操作】
    //输出 :(指针偏移量:0)_(加后:2)_2
    //      (指针偏移量:1)_1
}

数组名

  • 是一个地址信息(数组名是一个指针常量),而且还是数组第一个元素的地址;所以【数组/指针】也可以用【指针/数组】的方式访问元素。
  • 数组只能分开访问每一个元素(每一个元素都是左值);(函数名也有类似性质)
int main()
{
    int a[][3]={0};
    printf("a=%p\n",a);
    printf("a[0]=%p\n",a[0]);
    printf("&a[0][0]=%p\n",&a[0][0]);
}
//a=0x7ffdd6e40f14
//a[0]=0x7ffdd6e40f14
//&a[0][0]=0x7ffdd6e40f14

数组指针和二维数组

  二维数组

  • 每个元素都是一维数组;所以二维数组名是第一个一位数组的地址;(均是右值:地址常量)
  • 二维数组定义时可以不指定行数,但列数必须指定
int main()
{
    int a[][4]={1,2,3,4,5,6,7,8,9};
    //指定列,而不指定行时:系统会自动按列数划分行数
    int b[][4]={{1,2},{3,4},{5},{6,7,8,9}};
    //若赋值时有手动用'{}'划分行数时,遵循手动划分的行数
    printf("a[1][1]=%d  ",a[1][1]);
    printf("b[1][1]=%d",b[1][1]);
}
//输出:a[1][1]=6 b[1][1]=4
  • 二维数组是二级地址,指向的一维数组地址;一层解引用是一级指针,指向某行具体元素的地址;两层解引用就是指向元素的值
  • 解引用有两种方式(第三行首元素地址):*(a+2)、a[2]
int main()
{
    int z[][3]={1,2,3,4,5,6,7,8,9,666};
    printf("%d_%p\n",*(z)[0],&*(z)[0]);
    //第一行第一列的元素
    printf("%d_与首地址间距:%d\n",*(z+1)[2],&*(z+1)[2]-*z);
    //*(z+1)[2]:第4行第一列的元素
    //因为'*'解引用是从右向左运算,并且'[]'的优先级高于'*'
    //所以(z+1)[2]中的[2]和(+1)都是对z进行操作
    //所以跨度都是指向一维数组的大小
    printf("%d_与首地址间距:%d\n",*(z[2]+1),&*(z[2]+1)-*z);
    //*(z[2]+1):第三行第二列元素
    //先进行[2]的解引用,所以(+1)操作的是z一层解引用的指针(一级指针)跨度为元素大小
    //所以偏移地址是:向下移动2行,再向右移1个元素单位
}
int型二维数组a[][4]的有关指针
表示形式 含义 地址
a 二维数组名,指向一位数组a[0],即0行首地址 2000
a[0]、*(a+0)、*a 0行0列元素地址 2000
a+1、&a[1] 1行首地址 2016
a[1]、*(a+1) 1行0列元素a[1][0]的地址 2016
a[1]+2、*(a+1)+2、&a[1][2] 1行2列元素a[1][2]的地址 2024
*(a[1]+2)、*(*(a+1)+2)、a[1][2] 1行2列元素a[1][2]的值 元素值

  数组指针

  • 实质是一个指针,只不过是指向数组的指针(可以为其赋值二维数组或者一维数组的地址)
int main()
{
    int a[4]={1,2,3,4},b[2][4]={{1,2,3,4},{5,6,7,8}};
    int (*p)[4]=b;
    printf("%d__%d",p[0][3],p[1][0]);
    p=&a;
    printf("___%d",p[0][0]);
}
//输出:4__5___1
  • 数组指针偏移的跨度为指向数组的大小
int main()
{
    int a[4]={1,2,3,4};
    int (*p)[4]=&a;
    //int_4字节,指向长度为4的一维 int 数组;所以偏移跨度为16
    printf("%p__%p",p,p+1);
}
//输出:0x400440__0x400450
  • 由于数组指针偏移的跨度是指向数组的大小,所以二维数组和数组指针的使用方法可以互换:*( *( p+i ) + j ) == p[ i ][ j ];
int main()
{
    int a[][3]={1,2,3,4,5,6,7,8,9};
    int (*b)[3]=a;
    printf("a[1][2]=%d\n",a[1][2]);
    printf("*(a+1)[2]=%d\n",*(*(a+1)+2));
    printf("b[1][2]=%d\n",b[1][2]);
    printf("*(b+1)[2]=%d",*(*(b+1)+2));
}
//a[1][2]=6
//*(a+1)[2]=6
//b[1][2]=6
//*(b+1)[2]=6

 void(无类型)指针和NULL(空)指针

  • void *p:无类型指针可以指向任意类型的数据;解引用时需要强制类型转换;*(int *)p;
  • NULL 指针:不指向任何数据:
  • 其实NULL是一个宏定义:0  #define   NULL   ((void *)0) 
  • 当定义指针后不知道指向那个地址时就可以指向NULL【int *p=NULL;】

二级指针

  • 指向指针的指针,存储指针的地址;【一层解引用只为指向的级指针的地址】
  • 因为二级指针指向指针,二级指针移动的单位为指针大小
int main()
{
    int *a[4],**p=a;
    printf("&a[0]=%p p=%p\n",&a[0],p);
    printf("&a[2]=%p p+2=%p\n",a[0],p+2);
}
//输出:&a[0]=0019FF20 p=0019FF20
//      &a[2]=0019FF28 p+2=0019FF28
  • 终上所述,数组指针指向一个数组时,有*(*(p2+i)+j)==array[i][j];而二级指针则不会有这种性质。

常量和指针

  1、宏定义:define A 'a'
  2、使用 const 关键字(只读不写):const float pai=3.14;

指向常量的指针

const int *p;
//指向常量的指针,指向常量。不可以改变通过解引用修改指向地址的值;可以通过修改指向不同的常量来间接修改解引用的值
int * const p;
//指向常量指针,指向变量。不可以改变通过解引用修改指向地址的值;可以修改指向变量的值来间接修改解引用的值
const int * const p;
//指向常量的常量指针;【指向常量的指针也是可以指向变量的】
指针变量的类型及含义
变量定义 类型表示 含义
int i; int 定义整型变量i
int *p; int * 定义p为指向整型数据的指针变量
int a[5]; int [5] 定义整型数组a,他有5个元素
int *p[4]; int *[4] 定义指针数组p,它由4个指向整型数据的指针元素组成
int (*p)[4]; int (*)[4] p为指向包含4个元素的一维数组的指针变量
int f(); int () f为返回整型函数值的函数
int *p(); int *() p为返回一个指针的函数,该指针指向整型数据
int (*p)(); int (*)() p为函数指针,指向函数的指针,该函数返回一个整型数值
int **p; int ** p是一个指针变量,他指向一个指向整型数据的指针变量
void *p; void * p是一个指针变量,基类型为void(空类型),不指向具体的对象
  • 由于 '()' 的优先级高于 '*',所以 int *[4] 定义的是数组,去掉数组定义 [4] 剩下是数组元素类型( int 型指针);而 int (*)[4] 定义的是指针,去掉指针定义 (*) 剩下的是指针指向类型(长度为4的 int 型数组)
  • 指针函数与函数指针也是类似的定义方式