C语言——第四次笔记

  • 指针

    指针的定义
    指针的类型
    指针的指向内容
    指针的运算
    数组与指针
    指针与函数
  • 动态分配内存

  • 结构体

  • 文件读写

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

  • 文件操作训练之字符串查找

指针的定义

  • 指针是一个变量
  • 指针只能存地址
  • 指针占据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类型靠齐
    结构体A
  • 在结构体B当中,char ,int类型为double类型靠齐,由上自下的补齐,因为int类型占8位之后任有4位空着,这时候char会类型会自动补齐,占据剩下的4位
    结构体B
  • 在结构体Person当中,字符型指针和double相同大小,int类型向double靠齐,自上而下,没有空位让int类型补齐
    结构体Person
  • 在结构体Student当中,int类型和char类型向double靠齐,int类型分配8个字节,但前4位空着,char类型数组最后两位补齐,剩余两个空位
    结构体Student

文件读写

  • 打开文件
//fopen函数
FILE *fopen( const char * filename, const char * mode );
//fopen函数使用
FILE *fp = fopen("/Users/pengxiaodong/Desktop/test.txt", "r");
mode的值

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

推荐阅读更多精彩内容