数组
- 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语言中有两种表示字符串的方法:一是字符数组,二是字符指针指向字符串常量
- 读写能力不同:字符数组可以通过访问和修改数组内容,字符串常量只能通过指针访问字符串常量(只能读,不能写、修改)
- 存储位置不同:字符数组存储在栈中,字符串常量存储在静态存储区中(栈中数据访问速度高于静态区)
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个元素单位 }
| 表示形式 | 含义 | 地址 |
| 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 型数组)
- 指针函数与函数指针也是类似的定义方式
浙公网安备 33010602011771号