C语言学习(7)
一、C语言指针
1. 一维数组和指针的关系
第一种: int类型指针和int类型数组之间的关系
第二种: char类型指针和char类型数组之间的关系
规则: C语言规定,数组名出现在表达式中,代表该数组首元素的地址(数组名就是个指针,指向首元素的指针)
C语言规定,数组名取地址出现在表达式中,代表指向整个数组的一个指针,称作数组指针
#include <stdio.h> int main() { //int a[10]; char a[22]; //doube a[8]; printf("a的地址:%p\n", a); printf("a+1的地址:%p\n", a + 1); printf("&a的地址:%p\n", &a); //int (*)[10] printf("&a+1的地址:%p\n", &a + 1); //增加40字节 printf("&a[0]的地址:%p\n", &a[0]); printf("&a[0]+1的地址:%p\n", &a[0] + 1); }
总结: 一维数组和指针之间的关系有如下几种常见写法
写法一: char buf[10]="hello";
char *p=buf;
*(p+i); //i -->0到strlen(buf)
写法二: char buf[10]="hello";
char *p=buf;
p[i]; //i -->0到strlen(buf)
#include <stdio.h> /* %s -->要求后面是字符串的起始位置(指针) %c -->要求是普通变量的名字即可(不要求是指针) %d -->要求是普通变量的名字即可(不要求是指针) */ int main() { char buf[10] = "hello"; int otherbuf[10] = {78, 56}; char *p = buf; //&buf[0] printf("%s\n", buf); printf("%s\n", &buf[0]); printf("%s\n", p); //%s就是通过首地址,自动把整个字符串完整输出 printf("%s\n", &buf[2]); printf("%c\n", buf[0]); printf("%c\n", *p); int *q = otherbuf; //&otherbuf[0] }
2. 野指针
目前为止,C语言保持多个相同类型的数据有如下几种方法:
方法一:数组
方法二:指针
定义了指针,但是这个指针没有明确的指向,是不能使用的(很危险,容易产生段错误)
#include <stdlib.h>
void *malloc(size_t size); //给指针分配size个字节的内存空间,不会自动把内存空间清零
char *p=malloc(20); //给指针p分配20字节的内存空间
int *p=malloc(20); //给指针p分配20字节的内存空间
void free(void *ptr); //把之前操作系统分配给你内存空间释放(把内存还给操作系统)
free(p);
void *calloc(size_t nmemb, size_t size); //分配内存空间同时会自动把内存空间清零
char *p=calloc(5,10); //申请分配5块内存空间,每一块的大小是10个字节,总共分配5*10=50字节的空间
int *p=calloc(10,4); //10块内存空间,每一块大小是4字节
void *realloc(void *ptr, size_t size); //重新分配内存空间,改变之前分配的内存空间的大小
realloc(p,100); //重新给p申请100个字节的存储空间
注意:重新分配的内存首地址可能跟原来的地址一样,也可能不一样
#include <stdio.h> #include <string.h> #include <stdlib.h> int main() { int i; //用法一:char类型指针指向单个字符的地址 char a = '@'; char *p = &a; //定义指针p指向a的首地址 //用法二:char类型指针指向一个字符串的首地址(实际开发中用的最多) //第一种: 定义指针q,q指向字符串常量hello的起始地址 //char *q="hello"; //printf("q指向的字符串是:%s\n",q); //printf("q指向的是字符h在内存中的地址:%c\n",*q); //第二种: 定义指针q,指向char数组的首地址 //char buf[10]="world"; //char *q=buf; //数组名buf就是个指针,等价于 &buf[0] //char *q=&buf[0]; //printf("q指向的字符串是:%s\n",q); //printf("q指向的是字符串的起始地址:%c\n",*q); //传统做法,遍历数组通过下标来实现 //有了指针之后,通过指针来遍历访问数组成员了 //写法一: //for(i=0; i<strlen(buf); i++) //{ //printf("%c\n",*(q+i)); //利用了指针的加法 //} //写法二: //while((*q)!='\0') //{ //printf("*q is:%c\n",*q); //q++; //} //第三种: 定义指针,没有分配内存空间,就直接使用指针 //char *q; //q指向哪里不清楚,随机的,是个野指针 char *q = malloc(20); //分配20字节的空间给指针q printf("q指向的地址是:%p\n", q); printf("请输入一个字符串!\n"); scanf("%s", q); //很危险,万一q后面的地址非法,就完蛋了 printf("你输入的字符串是:%s\n", q); //释放刚才申请的内存空间 free(q); return 0; }
#include <stdio.h> #include <stdlib.h> int main() { int i; int *p = malloc(16); //16个字节空间,不会自动清零 *p = 56; *(p + 1) = 96; int *q = calloc(4, 4); //4块内存空间,每一块4字节,会自动清零 for (i = 0; i < 4; i++) { printf("*(p+%d) is:%d\n", i, *(p + i)); printf("*(q+%d) is:%d\n", i, *(q + i)); } //发现p指向的16字节空间不够用,想重新分配多一点 p = realloc(p, 160); printf("原本存放在p里面的内容也复制过来:%d\n", *p); printf("原本存放在p里面的内容也复制过来:%d\n", *(p + 1)); //发现q指向的16字节空间多了,想重新分配少一点 q = realloc(q, 4); //释放 free(p); free(q); }
3. 指针的加法和减法运算
指针的加法运算:(指针加上一个整数)
两个指针直接相加 --》没有任何实际意义,面试题也不会这么写
char buf[10];
char *p=buf; //指向首地址
p+1; // 初学者有错误的认识,把地址当成是数字+1
总结:指针做加法运算,加的是类型的大小
指针的减法运算:(指针减去一个整数)
两个指针直接相减---》只能是在数组里面两个指针相减有意义
计算公式: (p-q)结果是: (p-q)/指针类型的大小
总结:指针做减法运算,减的是类型的大小
#include <stdio.h> /* 指针的加法运算,加的是指针类型的大小 */ int main() { int a = 9; double b = 78; char c = 'u'; int *p1 = &a; double *p2 = &b; char *p3 = &c; printf("p1保存的地址:%p\n", p1); //int -->4字节 printf("p1+1的地址:%p\n", p1 + 1); //+4个字节 printf("p2保存的地址:%p\n", p2); //double -->8字节 printf("p2+1的地址:%p\n", p2 + 2); //+16字节 printf("p3保存的地址:%p\n", p3); //char -->1字节 printf("p3+1的地址:%p\n", p3 + 1); //+1字节 }
4. 空指针和void类型的指针
空指针: 在C语言中用NULL表示,在系统中NULL就是0x0000 0000地址
用法一:判断函数的返回值(返回指针),是否正确
void *malloc(size_t size);
char *p=malloc(10); //p可以存放10个字符
if(p==NULL) //说明malloc申请10个字节空间失败了
else //说明申请成功
int *q=malloc(20); //q可以存放5个整数
用法二:定义一个指针的时候,先让它指向NULL,后面改变指向
int *p=NULL; //p是个野指针,p由系统随机指向
p=&a;
scanf("%d",p)
void类型的指针: C语言表示通用类型指针
void类型的指针可以转换C语言其它类型的指针
void *malloc(size_t size); //void *通用性比较强
char *p=malloc();
int *p=malloc();
double *p=malloc();
5. 一维数组跟指针有关的几种写法
int a[5];
a //指针 int *
&a //指针,指向整个数组的一个指针(称作数组指针) int (*)[5]
&a[0] //指针 int *
a[0] //普通变量,不是指针
6. 数组指针和指针数组
数组指针:中心词是指针
定义: int a[5]; ---》类型 int [5]
int (*p)[5];
p=&a;
char b[9]; --》 类型 char [9]
char (*p)[9];
p=&b;
指针的一般定义:
公式: 类型 *名字
int *p;
char *p;
数组指针也符合这个公式
int a[100];
int (*p)[100]=a; //左右两边指针类型不一致
数组指针 int类型指针
int (*p)[100]=&a; //左右两边指针类型一致
数组指针 数组指针
#include <stdio.h> int main() { /* int (*p)[5]; //数组指针 int *q[5]; //指针数组 */ //最显著的区别--》数组指针,指针名字跟*加了圆括号 //定义int*类型的指针数组 int n1 = 48; int n2 = 89; int n3 = 96; int *a[5] = {&n1, &n2, &n3}; // a[0] --》&n1 printf("n1的地址:%p\n", a[0]); printf("n1的值:%d\n", *(a[0])); //定义char*类型的指针数组(存放多个字符串的首地址) char buf1[10] = "china"; char buf2[10] = "hello"; char buf3[10] = "world"; char *b[3] = {buf1, buf2, buf3}; printf("%s\n", b[0]); }
指针数组:中心词是数组
指针数组:本质上还是数组,只不过这个数组里面存放的全部都是相同类型的指针
前面学习的数组,里面存放的都是相同类型的普通变量
数组的一般定义:
公式: 类型 数组名[数组元素个数]
int a[10];
int * b[10]; // 定义了一个最多可以存放10个int *的数组
char *c[10]; //定义了一个最多可以存放10个char*的数组
作业:
1.
char *p;
int *p;
double *p sizeof(p) sizeof(*p) //看看大小是多少,为什么??
32位的ubuntu系统 ---》全部都是4个字节
64位的ubuntu系统 ---》全部都是8个字节
原因: 在64位系统上所有的地址长度都是8字节,而指针里面存放的就是其他变量的首地址,所以指针大小全部都一样
答:在32位系统计算机中则为4! 在64位系统计算机中则为8! 因为sizeof求得是所占的Byte数; 而p在该系统中内存随机分配,但是在不同系统中所占的位数不一样 所以说,sizeof(p) p是地址,地址位数在不同的系统中位数一样 若是sizeof(*p) *p解引用了对应的类型,所以所占的位数也不一样 char是1位字节 int是4位字节 double是8位字节
2.
char buf[10]=''gecchina";
char *p=&buf[3];
p=p+2;
*p='#';
printf("buf is:%s\n",buf);
答:最后输出为buf is:gecch#na 因为一开始*p的地址指向的是该数组的3位上,对应的字符为c, 后经过指针的加法运算,加多了2位,此时*p的地址指向为 该数组的5位上,对应的字符为i,经过解引用赋值, 将该字符替换了#,最后输出为gecch#na
3.
char buf[5][10]={"hello","world","china"};
char *p=&buf[1][3]; //world中l的地址
char *q=&buf[2][1]; //china中h的地址
printf("q-p is:%d\n",q-p);
总结: 指针相减表示它们之间间隔了多少个数据成员
答:最后输出为8。 *p的指针指向该数组的1,3 上,该字符为l *q的指针指向该数组的2,1上,该字符为h 假如该数组的首地址为0,这个数组总共有50位,即0-49; 则*p指的地址是该数组的12位; *q指的地址是该数组的20位; 输出做了个地址间的减法,则为20-12=8;