指针
指针的定义
指针的类型
指针的指向内容
指针的运算
数组与指针
指针与函数
动态分配内存
结构体
文件读写
头文件与实现文件实例之计算器
文件操作训练之字符串查找
指针
指针的定义
- 指针是一个变量
- 指针只能存地址
- 指针占据8个字节空间
总结:
指针是一种保存变量地址的变量
int main(){ int *a; char *b; printf("a的大小:%d\n", sizeof(a)); printf("a的地址:%p\n",a); printf("%d\n", sizeof(b)); } 输出: a的大小:8 a的地址:0000000000000001 8
- 指针的声明
int *p;//声明一个int类型的指针p char *p;//声明一个char类型的指针p int *arr[5];//声明一个指针数组,数组内5个元素,每个元素都是一个指向 int 类型对象的指针 int **p;//声明一个指针p,指针指向一个int类型的指针
- 指针的声明相对于普通变量的声明多了一个一元运算符 “
*
”。- 运算符 “
*
” 是间接寻址或者间接引用运算符。当它作用于指针时,将访问指针所指向的对象。- p 是一个指针,保存着一个地址,该地址指向内存中的一个变量;*p 则会访问这个地址所指向的变量。
- 声明一个指针变量并不会自动分配任何内存。
- 在对指针进行间接访问之前,指针必须进行初始化:或是使他指向现有的内存,或者给他动态分配内存,否则这个指针会变成野指针。
- 指针初始化
/* 方法1:使指针指向现有的内存 */ int a = 5; int *p = &a;
*
: 定义的时候表明是一个指针变量,使用的时候表示取地址的值
&
: 取某一个变量地址/* 方法2:动态分配内存给指针 */ int *p; p = (int *)malloc(sizeof(int) * 10); // malloc 函数用于动态分配内存 free(p); // free 函数用于释放一块已经分配的内存
指针的初始化实际上就是给指针一个合法的地址,让程序能够清楚地知道指针的指向,而不至于变为野指针
指针的类型
判断指针类型的方法:
去掉星号*和变量名就是指针的类型
int p;//P是一个普通的整型变量*
int *p;//P是一个返回整型数据的指针
P与*结合,所以说明P 是一个指针,然后再与int 结合,说明指针所指向的内容的类型为int型.
int p[3]; //所以P 是一个由整型数据组成的数组
P与[]结合,说明P 是一个数组,然后与int 结合,说明数组里的元素是整型的.
int *p[3]; //P 是一个由返回整型数据的指针所组成的数组
P与[]结合,因为其优先级比高,所以P 是一个数组,然后再与结合,说明数组里的元素是指针类型,然后再与int 结合,说明指针所指向的内容的类型是整型的.
int (*p)[3]; //P 是一个指向由整型数据组成的数组的指针
P与*结合,说明P 是一个指针然后再与[]结合(与"()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组,然后再与int 结合,说明数组里的元素是整型的.
int **p;//P是一个指向整型数的指针的指针(二级指针)
P与结合,说是P 是一个指针,然后再与结合,说明指针所指向的元素是指针,然后再与int 结合,说明该指针所指向的元素是整型数据.
int p(int);//P是一个参数和返回值都为int的一个函数
P与()结合,说明P 是一个函数,然后进入()里分析,说明该函数有一个整型变量的参数,然后再与外面的int 结合,说明函数的返回值是一个整型数据.
int (*p)(int);//P 是一个指向有一个整型参数且返回类型为整型的函数的指针
P与指针结合,说明P 是一个指针,然后与()结合,说明指针指向的是一个函数,然后再与()里的int 结合,说明函数有一个int 型的参数,再与最外层的int 结合,说明函数的返回类型是整型.
指针的指向内容
指针存储的内容为变量的地址,也就是说指针的是一个指向作用,指向变量所存储的内容
int main(){ int a = 5; int *p = &a; return 0; }
指针的运算
指针+(-)整数
可以对指针变量 p 进行 p++、p--、p + i 等操作,所得结果也是一个指针,只是指针所指向的内存地址相比于 p 所指的内存地址前进或者后退了 i (对应指针指向类型对应大小)个操作数。
int main(){ char a = '1'; char *p = &a; printf("p:%p\n",p); p++; printf("p++之后结果:%p\n",p); p--; printf("p--之后结果:%p\n",p); p+=5; printf("p+5之后结果:%p\n",p); return 0; } 输出: p:000000000062FE17 p++之后结果:000000000062FE18 p--之后结果:000000000062FE17 p+5之后结果:000000000062FE1C
p 是一个 char 类型的指针,指向内存地址0062FE17处。则 p++ 将指向与 p 相邻的下一个内存地址,由于 int 型数据占 4 个字节,因此 p++ 所指的内存地址为 1000000b。其余类推。不过要注意的是,这种运算并不会改变指针变量 p 自身的地址,只是改变了它所指向的地址
数组与指针
- 数组的内存空间:
数组的数组名其实可以看作一个指针,因为数组名是指向数组的第一个元素,上面num数组指向的也就是第一个元素1,数组名本身是没有占有内存的int array[10]={0,1,2,3,4,5,6,7,8,9},value; value=array[0]; //也可写成:value=*array; value=array[3]; //也可写成:value=*(array+3); value=array[4]; //也可写成:value=*(array+4);
另外一种解释是将数组名指向数组的第0个单元,那么(array+n)也就是一个指向数组里的第n个单元的指针
int main(){ int num[9] = {1,2,3,4,5,6,7,8,9}; int *p = num; *p++; int a = (*p)++; //2 int b = *(p++); //3 printf("%d\n%d\n",a, b); 输出: 2 3
p指向的是数组的首地址,也就是数组的第一个元素,那么p++之后也就是对指针p前进了4(int类型)个操作数,而数组是分配了连续空间,所以相对地址是加减也就是数组元素的位置变换
指针数组
指针数组, 是一个数组,数组中的每一个元素都是指针
int *data[10]={NULL};//注意,一定要初始化 for(int i = 0; i < 10; ++ i){ data[i] = (int*)malloc(sizeof(int) * 10); } data[1][2] = 1;
对于上面的定义和初始化, data是指针数组的名字, 也就是指向指针数组首元素的指针. (指针的指针). data[i] 是data这一个数组的第i个元素, 也就是一个指向int的指针
指针可以当成数组来使用,data[i][j]
和*(data[i]+j)
是等价经过上述代码创建的一个指针数组data的使用和int data[10][10]基本相同, 区别在于后者保证数组和数组之间的内存地址是连续的. data[0][9] 和 data[1][0] 是连续的, 而如果使用指针数组方式创建的data, 不能保证 data[0][9] 和 data[1][0] 在内存上连续
数组指针
数组指针,是一个指针,它指向一个数组
int (*)data[10] = NULL;//一个指向长度为10的int数组(int [10])的指针 //一般, 我们并不会使用到数组指针 //一般使用: int func(int data[][20]){ }
数组作为参数传入函数的时候, 对于被调用的函数参数就是指针. 因此, 这里参数是一个"元素为
int[20]
"的数组(数组的数组), 因此, 在函数内部, data实际上就是一个"指向int[20]
"的指针(int(*)[20])
- 尽量不要对数组和指针使用
sizeof
- 当且仅当如
malloc(10*sizeof(int))
时使用sizeof
指针与函数
- 函数指针是指向函数的指针变量
- 通常我们说的指针变量是指向一个整型、字符型或数组等变量,而函数指针是指向函数
- 函数指针可以像一般函数一样,用于调用函数、传递参数
函数指针声明typedef int (*fun_ptr)(int,int); // 声明一个指向同样参数、返回值的函数指针类型
下面实例声明了函数指针变量 p,指向函数 max:
#include <stdio.h> int max(int x, int y){ return x > y ? x : y; } int main(void){ /* p 是函数指针 */ int (* p)(int, int) = & max; // &可以省略 int a, b, c, d; printf("请输入三个数字:"); scanf("%d %d %d", & a, & b, & c); /* 与直接调用函数等价,d = max(max(a, b), c) */ d = p(p(a, b), c); printf("最大的数字是: %d\n", d); return 0; } 输出 请输入三个数字:1 2 3 最大的数字是: 3
函数指针变量可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数。也就是说回调函数是由别人的函数执行时调用你实现的函数
- 下面事例中 populate_array 函数定义了三个参数,其中第三个参数是函数的指针,通过该函数来设置数组的值。
- 实例中我们定义了回调函数 getNextRandomValue,它返回一个随机值,它作为一个函数指针传递给 populate_array 函数。
- populate_array 将调用 10 次回调函数,并将回调函数的返回值赋值给数组
#include <stdlib.h> #include <stdio.h> // 回调函数 void populate_array(int *array, size_t arraySize, int (*getNextValue)(void)){ for (size_t i=0; i<arraySize; i++) array[i] = getNextValue(); } // 获取随机值 int getNextRandomValue(void){ return rand(); } int main(void){ int myarray[10]; populate_array(myarray, 10, getNextRandomValue); for(int i = 0; i < 10; i++) { printf("%d ", myarray[i]); } printf("\n"); return 0; } 输出: 41 18467 6334 26500 19169 15724 11478 29358 26962 24464
动态分配内存
- 动态分配内存的原因
1.存储的数据 需要延长生命周期
2.一个指针变量需要存储数据,变量本身只能存地址,不能存数据,需要分配内存空间来存储数据
- C 语言为内存的分配和管理提供了几个函数(导入库为<stdlib.h>)
- 内存分配
如果使用指针变量接收数据,必须先为这个指针变量分配一片指向的内存空间
char *name ;
用malloc(memory alloc)申请内存空间
name = (char *)malloc(10*sizeof(char));
使用realloc动态改变已经分配内存的大小
name = (char *)realloc(name, 20*sizeof(char));
使用完毕必须自己手动释放内存
free(name);
结构体
结构体的优势
可以存储多种数据数据的变量
结构体定义
struct student{//定义一个学生结构体 int age; char sex; char name[10]; }:
- student是结构体名称
- int age等是标准的变量定义
结构体定义变量
struct student LiMing;//struct student是一种结构体类型类似于int,float类型等 struct student *p = &LiMing;
结构体的访问
LiMing.age = 18; LiMing.sex ='m'; LiMing.name ="李明"; //指针使用->访问元素 p->age = 29; p->sex = 'f';
结构体内存大小计算
对齐方式
- 内存小的数据类型向内存大的数据类型对齐
int main(){ struct A{ char a; int b; }; struct B{ double a; int b; char c; }; struct Person{ char *name; double score; int age; }; struct Student{ char name[10]; int age; double score; }; printf("%d %d\n",sizeof(struct A),sizeof(struct B)); printf("%d %d\n",sizeof(struct Person),sizeof(struct Student)); return 0; } 输出: 8 16 24 24
- 在结构体A当中,char类型向int类型靠齐
- 在结构体B当中,char ,int类型为double类型靠齐,由上自下的补齐,因为int类型占8位之后任有4位空着,这时候char会类型会自动补齐,占据剩下的4位
- 在结构体Person当中,字符型指针和double相同大小,int类型向double靠齐,自上而下,没有空位让int类型补齐
- 在结构体Student当中,int类型和char类型向double靠齐,int类型分配8个字节,但前4位空着,char类型数组最后两位补齐,剩余两个空位
文件读写
- 打开文件
//fopen函数 FILE *fopen( const char * filename, const char * mode ); //fopen函数使用 FILE *fp = fopen("/Users/pengxiaodong/Desktop/test.txt", "r");
- 写入文件
//fputc函数 int fputc( int c, FILE *fp ); //函数使用 fputc('a', fp); //fputs函数 int fputs( const char *s, FILE *fp );//按照一定的格式写入内容 //函数使用 fputs("jack", fp);
- 读取文件
//fgetc函数 int fgetc( FILE * fp ); //函数使用 fgetc(fp);
- 关闭文件
//关闭文件 int fclose( FILE *fp ); //fclose函数使用 fclose(fp);
头文件与实现文件实例之计算器
- 计算器的头文件Calculator.h
#include <stdio.h> //头文件里面声明函数 //加法 int add(int a, int b); //减法 int minus(int a, int b); //乘法 int multiply(int a, int b); //除法 int devide(int a, int b);
- 计算器的实现函数Calculator.cpp
//实现文件 //1. 先导入需要实现的头文件 //2. 实现这个头文件里面的所有方法 #include "Calculator.h" //加法 i>nt add(int a, int b){ return a + b; } //减法 int minus(int a, int b){ return a - b; } //乘法 int multiply(int a, int b){ return a * b; } //除法 int devide(int a, int b){ if (b == 0){ return 0; }else{ return a / b; } }
- 计算器main函数入口
#include <stdio.h> //1.程序的入口函数 //main.cpp 为了让阅读者 //知道我这里面写的是入口函数 //2. 将不同的功能模块用不用的.h .cpp来封装 //.h 头文件 函数声明 (不能实现) //.cpp .c 实现文件 函数的具体实现{} //3.导入头文件进行使用 #include <stdio.h> //头文件里面声明函数 //加法 int add(int a, int b); //减法 int minus(int a, int b); //乘法 int multiply(int a, int b); //除法 int devide(int a, int b); /* 1.预编译 */ int main(){ int result = add(1,1); printf("1 + 1 = %d\n", result); printf("1 + 1 = %d\n", add(1,1)); printf("2 - 1 = %d\n", minus(2,1)); printf("2 * 2 = %d\n", multiply(2,2)); printf("2 / 2 = %d\n", devide(2,2)); return 0; } void test(){ }
文件操作训练之字符串查找
#include <stdio.h> #include <stdlib.h> #include <stdbool.h> //从终端接收字符串 返回这个字符串的首地址 char *inputName(){ //1.定义一个指针变量 指向字符串的首地址 char *pName = NULL; //2.接收输入 int i = 0; //3.提示操作 printf("请输入人名:"); while (1) { //接收一个字符 char c = getchar(); //判断这个字符是不是\n if (c == '\n') { //输入结束 break; } //判断是不是第一个字符 if(i == 0){ //使用malloc分配内存 pName = (char *)malloc(1*sizeof(char)); //判断是否分配成功 if(pName == NULL){ exit(EXIT_FAILURE); } pName[0] = c; }else{ //使用realloc在之前的基础上加一个 pName = realloc(pName, (i+1)*sizeof(char)); //判断是否分配成功 if(pName == NULL){ exit(EXIT_FAILURE); } pName[i] = c; } i++; } //将当前的字符串首地址返回 return pName; } //是否继续 bool isContinue(){ printf("是否继续(y/n)?:"); while (1) { char c = getchar(); getchar(); if (c == 'y'){ return true; }else if(c == 'n'){ return false; }else{ printf("输入格式不对,请重新输入:"); } } } //初始化整个数组 char **initNames(int *pNum){ //1.定义指针变量指向每个名字的首地址的内存 char **pHead = NULL; //2.记录元素个数 int i = 0; while (1) { //判断是不是第一个 //第一个使用malloc分配内存 if (i == 0) { pHead = malloc(1*sizeof(char *)); if (pHead == NULL) { exit(EXIT_FAILURE); } //输入人名 将地址放到pHead对应位置 pHead[0] = inputName(); }else{ //使用realloc重新再增加一个元素 pHead = realloc(pHead, (i+1)*sizeof(char *)); if (pHead == NULL) { exit(EXIT_FAILURE); } //输入人名 将地址放到pHead对应位置 pHead[i] = inputName(); } i++; //是否继续 bool result = isContinue(); if (result == false) { break; } } *pNum = i; return pHead; } void show(char **pHead, int num){ printf("输入%d个名字:\n",num); int i; for ( i = 0; i < num; i++) { printf("%s\n",pHead[i]); } printf("\n"); } int main(int argc, const char * argv[]) { char **pHead = NULL; int count = 0; pHead = initNames(&count); show(pHead, count); return 0; }