C语言基础(四)

目标:掌握用C语言编程的基本技能
内容:1. 指针的定义
   2. 指针的类型
   3. 指针的指向内容
   4. 指针的运算
   5. 指针与数组
   6. 指针与函数
   7. 动态分配内存
   8. 结构体
   9. 文件读写
   10. 头文件与实现文件实例之计算器实战
   11.文件操作训练之字符串查找实战

指针的定义

  1. 指针是一个只能存地址的变量,是一种保存变量地址的变量,占据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
*/
  1. 指针的声明
  • 指针的声明相对于普通变量的声明多了一个一元运算符“ * ”
  • 运算符“ * ”是间接寻址或者间接引用运算符。当它作用于指针时,将访问指针所指向的对象
  • p 时一个指针,保存着一个地址,该地址指向内存中的一个变量;*p 则会访问这个地址所指向的变量
  • 声明一个指针变量并不会自动分配任何内存
  • 在对指针进行间接访问之前,指针必须进行初始化;或是使它指向现有的内存,或者给它动态分配内存
  int *p;//声明一个int类型的指针p 
  char *p;//声明一个char类型的指针p 
  int *array[5];//声明一个数组指针,数组内有5个元素,每个元素都是一个指向>int类型对象的指针 
  int **p;//声明一个指针p,指针指向一个int类型的指针 
  1. 指针初始化
  • 指针的初始化实际上就是给指针一个合法的地址,让程序能够清楚地知道指针的指向
  • *:定义的时候表明是一个指针变量;使用的时候表示取地址的值
    &:取某一个变量地址
//方法一:使指针指向现有内存
  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
*/

指针与数组

  1. 区别:数组是一片连续的内存空间,指针只是一个变量(存地址)
  2. 数组的数组名其实可以看作一个指针,因为数组名是指向数组第一个元素的,数组名本身不占有内存
  • 将数组名指向数组的第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 
*/
  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};
  for(int i = 0;i < 10;++i){
      data[i] = (int*)malloc(sizeof(int) * 10);
  }
  data[1][2] = 1;    
  1. 数组指针
  • 数组指针是一个指针,它指向一个数组
  • 数组作为参数传入函数的时候,对于被调用的函数参数就是指针。因此,这里参数是一个“元素为int[20]”的数组(即数组的数组),所以在函数内部,data实际上就是一个“指向int[20]”的指针(int(*)[20])
int (*)data[10] = NULL;//一个指向长度为10的int数组的指针
  //一般,我们并不会使用数组指针
  //一般使用: 
  int func(int data[][20]){
  }   

注意:尽量不要对数组和指针使用sizeof,当且仅当如malloc(10*sizeof(int))时使用

指针与函数

  1. 函数指针是指向函数的指针变量,它可以像一般函数一样,用于调用函数、传递参数。通常我们说的指针变量是指向一个整型、字符型或者数组等变量,而函数指针是指向函数
  2. 函数指针的声明
typedef int(*fun_ptr)(int,int);//声明一个指向同样参数、返回值的函数指针类型
  1. 下面的实例声明了函数指针变量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 
*/
  1. 函数指针变量可以作为某个函数的参数来使用,回调函数就是一个通过函数指针调用的函数。也就是说回调函数是由别人的函数执行时调用你实现的函数
  • 下面的实例中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
*/  

动态分配内存

  1. 需动态分配内存的情况:
  • 存储的数据需要延长生命周期
  • 一个指针变量需要存储数据,变量本身只能存地址,不能存数据,需要动态分配内存空间来存储数据
  1. 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
  1. 内存分配
  • 如果使用指针变量接收数据,必须先为这个指针变量分配一片指向的内存空间
char *name;
  • 用malloc(memory alloc)申请内存空间
name = (char *)malloc(10*sizeof(char));
  • 使用realloc动态改变已经分配内存的大小
name = (char *)realloc(name,20*sizeof(char));
  • 使用完毕必须自己手动释放内存
free(name)

结构体

  1. 结构体的优势:可以存储多种数据的变量
  2. 结构体的定义:
    student是结构体名称,int age等是标准的变量定义
struct student{//定义一个学生结构体
       int age;
       char sex;
       char name[10];
  };
  1. 结构体定义变量
struct student LiMing;//struct student是一种结构体类型,类似于int、float类型等
struct student *p = &LiMing;
  1. 结构体的访问
LiMing.age = 18;
LiMing.sex = 'm';
LiMing.age = "李明";
//指针使用->访问元素
p->age = 29;
p->sex = 'f';
  1. 结构体内存大小的计算
    内存小的数据类型向内存大的数据类型对齐
  • 在结构体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 
*/

文件读写

  1. 写入文件
//fputc函数
  int fputc(int c,FILE *fp);
  //函数使用
  fputc('a',fp);
  //fputs函数
  int fputs(const char *s,FILE *fp);//按照一定格式写入内容
  //函数使用
  fputs("jack",fp); 
  1. 读取文件
//fgetc函数
int fgetc(FILE *fp);
//函数使用
fgetc(fp);
  1. 打开文件
//fopen函数
FILE *fopen(const char *filename,const char *mode);
//函数使用
FILE *fp = fopen("//Users//Aurora//test.txt", "r+");
  • r :打开一个已有的文本文件,允许读取文件
  • w :打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序从文件的开头写入内容;如果文件存在,则该文件会被截断为零长度,重新写入
  • a :打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会在已有文件内容中追加内容
  • r+ :打开一个文本文件,允许读写文件
  • w+ :打开一个文本文件,允许读写文件。如果文件存在,则该文件会被截断为零长度;如果文件不存在,则会创建一个新文件
  • a+ :打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式
  1. 关闭文件
//fclose函数
int fclose(FILE *fp);
//函数使用
fclose(fp);

头文件与实现文件实例之计算器实战

  1. 计算器的头文件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);
  1. 计算器的实现函数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;
  }
} 
  1. 计算器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;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容