目标:掌握用C语言编程的基本技能
内容:1. 指针的定义
2. 指针的类型
3. 指针的指向内容
4. 指针的运算
5. 指针与数组
6. 指针与函数
7. 动态分配内存
8. 结构体
9. 文件读写
10. 头文件与实现文件实例之计算器实战
11.文件操作训练之字符串查找实战
指针的定义
- 指针是一个只能存地址的变量,是一种保存变量地址的变量,占据8个字节空间
#include <stdio.h> int main(){ int *a; char *b; printf("a的大小:%d\n",sizeof(a)); printf("a的地址:%p\n",a); printf("%d\n",sizeof(b)); return 0; } /*输出: a的大小:8 a的地址:0000000000000001 8 */
- 指针的声明
- 指针的声明相对于普通变量的声明多了一个一元运算符“ * ”
- 运算符“ * ”是间接寻址或者间接引用运算符。当它作用于指针时,将访问指针所指向的对象
- p 时一个指针,保存着一个地址,该地址指向内存中的一个变量;*p 则会访问这个地址所指向的变量
- 声明一个指针变量并不会自动分配任何内存
- 在对指针进行间接访问之前,指针必须进行初始化;或是使它指向现有的内存,或者给它动态分配内存
int *p;//声明一个int类型的指针p char *p;//声明一个char类型的指针p int *array[5];//声明一个数组指针,数组内有5个元素,每个元素都是一个指向>int类型对象的指针 int **p;//声明一个指针p,指针指向一个int类型的指针
- 指针初始化
- 指针的初始化实际上就是给指针一个合法的地址,让程序能够清楚地知道指针的指向
- *:定义的时候表明是一个指针变量;使用的时候表示取地址的值
&:取某一个变量地址//方法一:使指针指向现有内存 int a = 1; int *p = &a; // 方法二:动态分配内存给指针 int *p; p = (int *)malloc(sizeof(int) * 10); //malloc函数用于动态分配内存 free(p); //free函数用于释放一块已经分配的内存
指针的类型
判断指针类型的方法:去掉 * 和变量名就是指针的类型
p与*结合,说明p是一个指针,然后再与int结合,说明指针所指向内容的类型位int型
int p;//p是一个普通的整型变量 int *p;//p是一个返回整型数据的指针
- p与[ ]结合,说明p是一个数组,然后与int结合说明数组里的元素是整型的
- p与[ ]结合,因为其优先级比* 高,所以p是一个数组,然后再与 *结合,说明数组里的元素是指针类型,再然后与int结合,说明数组里的元素是整型的
- p与*结合,说明p是一个指针,然后再与[ ]结合,说明指针指向的内容是一个数组,再然后与int结合,说明数组里的元素是整型的(与()结合这一步可以忽略,只是为了改变优先级)
int p[3];//p是一个由整型数据组成的数组 int *p[3];//p是一个由返回整型数据的指针所组成的数组 int (*p)[3];//p是一个指向由整型数据组成的数组的指针
p与* 结合,说明p是一个指针,然后再与 *结合,说明指针所指向的元素是指针,再然后与int结合,说明该指针所指向的元素是整型数据
int **p;//p是一个指向整型数的指针的指针(二级指针)
- p与()结合,说明p是一个函数,然后进入()里分析,说明该函数有一个整型变量的参数,再然后与外面的int结合,说明函数的返回值是一个整型数据
- p与*结合,说明p是一个指针,然后与()结合,说明指针指向的是一个函数,再然后与()里的int结合,说明函数有一个int型的参数,再与最外面的int结合,说明函数的返回类型是整型
int p(int);//p是一个参数和返回值都为int的函数 int (*p)(int);//p是一个指向有一个整型参数且返回类型为整型的函数的指针
指针的指向内容
指针存储的内容为变量的地址,也就是说指针其一个指向作用,指向变量所存储的内容
int a = 3; int *p = &a;
指针的运算
可以对指针变量p进行p++、p--、p+i等操作,所得结果也是一个指针,只是指针所指向的内存地址相比于p所指的内存地址前进或者后退了i(对应指针指向类型对应的大小)个操作数
注意:这种运算并不会改变指针变量p自身的地址,只是改变了它所指向的地址#include <stdio.h> 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 */
指针与数组
- 区别:数组是一片连续的内存空间,指针只是一个变量(存地址)
- 数组的数组名其实可以看作一个指针,因为数组名是指向数组第一个元素的,数组名本身不占有内存
- 将数组名指向数组的第0个单元,那么(array+n)也就是一个指向数组里的第n个单元的指针
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);
- p指向的是数组的首地址,也就是数组的第一个元素,那么p++之后也就是对指针p前进了4(int类型)个操作数,而数组是分配了连续空间的,所以相对地址的加减也就是数组元素的位置变换
#include <stdio.h> 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); return 0; } /*输出: 2 3 */
- 指针数组
- 指针数组是一个数组,数组中的每一个元素都是指针
- 对于下面的定义和初始化,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}; for(int i = 0;i < 10;++i){ data[i] = (int*)malloc(sizeof(int) * 10); } data[1][2] = 1;
- 数组指针
- 数组指针是一个指针,它指向一个数组
- 数组作为参数传入函数的时候,对于被调用的函数参数就是指针。因此,这里参数是一个“元素为int[20]”的数组(即数组的数组),所以在函数内部,data实际上就是一个“指向int[20]”的指针(int(*)[20])
int (*)data[10] = NULL;//一个指向长度为10的int数组的指针 //一般,我们并不会使用数组指针 //一般使用: int func(int data[][20]){ }
注意:尽量不要对数组和指针使用sizeof,当且仅当如malloc(10*sizeof(int))时使用
指针与函数
- 函数指针是指向函数的指针变量,它可以像一般函数一样,用于调用函数、传递参数。通常我们说的指针变量是指向一个整型、字符型或者数组等变量,而函数指针是指向函数
- 函数指针的声明
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 <stdio.h> #include <stdlib.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\n",myarray[i]); } return 0; } /*输出: 41 18467 6334 26500 19169 15724 11478 29358 26962 24464 */
动态分配内存
- 需动态分配内存的情况:
- 存储的数据需要延长生命周期
- 一个指针变量需要存储数据,变量本身只能存地址,不能存数据,需要动态分配内存空间来存储数据
- C语言为内存的分配和管理提供了以下函数(导入库为<stdlib.h>)
- void *calloc(int num,int size);在内存中动态地分配num个长度为size的连续空间,并将每一个字节都初始化为0,所以它的结果是分配了num * size个字节长度的内存空间,并且每个字节的值都是0
- void *malloc(int num);在堆区分配一块指定大小的内存空间,用来存放数据,这块内存空间在函数执行完成后不会被初始化,它们的值是未知的
- void free(void *address);该函数释放address所指向的内存块,释放的是动态分配的内存空间
- void *realloc(void *address,int newsize);该函数重新分配内存,把内存扩展到newsize
- 内存分配
- 如果使用指针变量接收数据,必须先为这个指针变量分配一片指向的内存空间
char *name;
- 用malloc(memory alloc)申请内存空间
name = (char *)malloc(10*sizeof(char));
- 使用realloc动态改变已经分配内存的大小
name = (char *)realloc(name,20*sizeof(char));
- 使用完毕必须自己手动释放内存
free(name)
结构体
- 结构体的优势:可以存储多种数据的变量
- 结构体的定义:
student是结构体名称,int age等是标准的变量定义struct student{//定义一个学生结构体 int age; char sex; char name[10]; };
- 结构体定义变量
struct student LiMing;//struct student是一种结构体类型,类似于int、float类型等 struct student *p = &LiMing;
- 结构体的访问
LiMing.age = 18; LiMing.sex = 'm'; LiMing.age = "李明"; //指针使用->访问元素 p->age = 29; p->sex = 'f';
- 结构体内存大小的计算
内存小的数据类型向内存大的数据类型对齐
- 在结构体A中,char类型向int类型靠齐
- 在结构体B中,char、int类型向double类型靠齐,由上而下地补齐,因为int类型占4位之后仍有4位空着,这时候char类型会自动补齐
- 在结构体Person中,字符型指针和double相同大小,int类型向double靠齐,自上而下,没有空位让int类型补齐
- 在结构体Student中,int类型和char类型向double靠齐,int类型分配8个字节,但前4位空着,char类型数组最后两位补齐,剩余2个空位
#include <stdio.h> 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 */
文件读写
- 写入文件
//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);
- 打开文件
//fopen函数 FILE *fopen(const char *filename,const char *mode); //函数使用 FILE *fp = fopen("//Users//Aurora//test.txt", "r+");
- r :打开一个已有的文本文件,允许读取文件
- w :打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序从文件的开头写入内容;如果文件存在,则该文件会被截断为零长度,重新写入
- a :打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会在已有文件内容中追加内容
- r+ :打开一个文本文件,允许读写文件
- w+ :打开一个文本文件,允许读写文件。如果文件存在,则该文件会被截断为零长度;如果文件不存在,则会创建一个新文件
- a+ :打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式
- 关闭文件
//fclose函数 int fclose(FILE *fp); //函数使用 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" //加法 int 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函数入口main.cpp
#include <stdio.h> //1.程序的入口函数 //main.cpp 为了让阅读者知道我在这里面写的是入口函数 //2.将不同的功能模块用不同的 .h .cpp 来封装 //.h 头文件 函数声明(不能实现) //.c .cpp 实现文件 函数的具体实现{} //3. 导入实现文件进行使用 #include "calculator.cpp" 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; } /*运行结果: 1 + 1 = 2 1 + 1 = 2 2 - 1 = 1 2 * 2 = 4 2 / 2 = 1 */
文件操作训练之字符串查找实战
#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); for (int 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; }