C语言进阶

        一些C语言相对进阶知识点的总结

目录

一、指针基础

二、函数

三、文件

四、结构体

五、预处理

六、内存布局


一、指针基础

1、 指针概念:

类型符 *指针变量  例如:int *p

p:指针变量;*p:指针指向的地址的值;

**p:指向指针的指针:

p是二级指针变量,其中存放着i的地址0x00000004,p也有地址,是0x00000000;

*p= i= 0x00000008; //p解引用也就是i的内容

**p= *i = "一段内容"; //i解引用,也就是i指针指向的d地址上的值

p= &i = 0x00000004; //p存的是i的地址,i的地址是0x00000004

&p = 0x00000000; //p取地址

// * ”用法:1、表示乘法,例如c=a*b

                  2、定义指针变量,例如:int *p,a;其中p是指针变量,a是整型变量。

                  3、表示获取指针指向的数据,是一种间接操作,例如:int*p=&a,*p=100;

//指针变量保存的是地址本质上也是个整型数!所以指针变量可以进行部分运算,例如加、减、比较等.

2、数组指针和指针数组。

数组指针:类型名 *数组名)[元素个数] 例如:int*p[3];

  1. 这个指针存放着一个数组的首地址,或者说这个指针指向一个数组的首地址.
  2. 常常用于地址传参,用来接收二维数组的首地址

指针数组:类型名 *指针名 [元素个数]   例如:int *p[3];

  1. 可以用于记录多个字符串,快捷使用。
  2. 指针数组常用在主函数传参,在写主函数时,参数有两个,一个确定参数个数,一个这是指针数组用来接收每个参数(字符串)的地址。
  3. 如果是向子函数传参,这和传递一个普通数组的思想一样,不能传递整个数组过去,如果数组很大,这样内存利用率很低,所以应该传递数组的首地址,用一个指针接收这个地址。因此,指针数组对应着二级指针

3、函数指针和指针函数。

函数指针:指向函数入口地址的指针。  

数据类型  指针变量名)(函数参数表) 例如: int (*p) (int ,int);

  1. 函数指针的值就是指针所指向的函数的返回值
  2. int (*p) (int ,int);仅仅定义了一个指针,它可以指向不同的函数,只要该函数返回一个整型数据,且有两个整型参数。
  3. p+np++p--无意义

指针函数:函数返回值是指针的函数。

4、结构体指针:是该结构体变量所占据内存段的起始地址。也可以用来指向结构体数组中的元素。      

                  struct 结构名 *结构指针变量名 例如 struct student *p

5、文件指针:指向一个包含文件信息的结构体,

包括: 缓冲区的地址  缓冲区中当前字符的位置  文件的访问模式

                    FILE *fp

6、指针访问数组的方式

 p++
   (*p).成员名称
   p->成员名称 

p+i
   (*(p+i)).成员名称;
   (p+i) -> 成员名称

 p[i]
    p[i].成员名称

二、函数

//函数可以通过多文件编程使用存放在其他C文件里的函数,使用时该函数所在的文件需要声明头函数。

1.内置函数:编写程序时可以直接使用的函数,是C库中自带

2.自定义函数:用户自己定义的可以实现用户需要的功能的函数,使用前如果没有定义需要提前声明。

3.嵌套和递归:函数也可以嵌套和递归使用,但是在使用的时候需要留一个跳出嵌套或者递归的出口。

4.返回值:被使用函数运行结束后返回到使用函数的值,类型为函数名定义,如果返回值和返回值类型不匹配,以返回值类型为准。一次最多返回一个值。在被使用函数中起到的作用多为结束函数。关键词为:return 返回值;

//注:无返回值函数(void 函数)没有返回值

5.传参:

  a.数值传参:将主函数中的参数的数值传递到被使用函数中去;

例如:

  1 #include
  2 
  3 void sum_passed(int a)
  4 {
  5     a=10;
  6     printf("被使用函数中的a=%d\n",a);
  7 }
  8 int main()
  9 {
 10     int a=0;
 11 
 12     sum_passed(a);
 13 
 14     printf("主函数中的a=%d\n",a);
 15     return 0;
 16 }

得到的结果是

被使用函数中的a=10
主函数中的a=0

b.地址传参:将主函数中元素的地址传递到被使用函数中,这样可以直接通过地址改变地址所保存的数。也就是说,如果被使用函数可能需要改变主函数中参数的值,就把主函数中参数的值的地址传递过去。

例如:

#include
  2 
  3 void sum_passed_1(int a)
  4 {
  5     a=10;
  6     printf("被使用函数中的a=%d\n",a);
  7 }
  8 void address_passed(int *p)
  9 {
 10     *p=20;
 11     printf("被使用函数中的*p=%d\n",*p);
 12 }
 13 void sum_passed_2(int *q)
 14 {
 15     int b=30;
 16     q=&b;
 17     printf("指针之间的数值传参*q=%d\n",*q);
 18 }
 19 int main()
 20 {
 21     int a=0;
 22     int *p=&a;
 23 
 24     address_passed(&a);
 25     printf("主函数中的a=%d\n",a);
 26 
 27     sum_passed_2(p);
 28     printf("主函数中的*p=%d\n",*p);
 29     return 0;
 30 }

结果是

被使用函数中的*p=20
主函数中的a=20
指针之间的数值传参*q=30
主函数中的*p=20

注意这里主函数的*p和被调用函数的*q的值不同,为什么呢?

调用函数前

C语言进阶_第1张图片

 调用函数时

C语言进阶_第2张图片

 函数结束后

C语言进阶_第3张图片

 于是就出现了*p*q不同值得情况

如果需要避免这个情况,只需要使用二级指针作为函数形参,道理和一级指针接收变量一致

数组作为参数进行传参,理同指针,直接传递数组首地址,形参使用指针,使用p[]或者*p接收数组首地址,如果实参是二维数组就使用数组指针(*p[])接收。 

6.局部变量和全局变量:

局部变量 静态局部变量 全局变量 静态全局变量
生存期       

变量定义

     到

函数结束

变量定义

     到

程序结束

程序开始

      到

程序结束

程序开始

      到

程序结束

作用域 函数内部 函数内部 所有.c文件 仅本c.文件

生存期:  变量定义  ---->  变量销毁
作用域:  变量的可访问性

三、文件

//FILE*fp

1.打开文件

fp=fopen(“./file”,”w\r\a”);
       if(NULL ==fp)
       {
              perror("fopen");
              return -1;
       }
  • r 以只读方式打开文件,该文件必须存在。
  • r+ 以可读写方式打开文件,该文件必须存在。
  • rb+ 读写打开一个二进制文件,允许读数据。
  • rw+ 读写打开一个文本文件,允许读和写。
  • w 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。
  • w+ 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
  • a 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留)
  • a+ 以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。 (原来的EOF符不保留)
  • wb 只写打开或新建一个二进制文件;只允许写数据。
  • wb+ 读写打开或建立一个二进制文件,允许读和写。
  • ab+ 读写打开一个二进制文件,允许读或在文件末追加数据。
  • at+ 打开一个叫string的文件,a表示append,就是说写入处理的时候是接着原来文件已有内容写入,不是从头写入覆盖掉,t表示打开文件的类型是文本文件,+号表示对文件既可以读也可以写。

2. 文件的格式化输入输出

              fprintf(fp, "%d\t%s\t%lf\n", s.num, s.name, s.score);

                fp :指针指向的文件;

               "%d\t%s\t%lf\n"输出的数据格式

               s.num, s.name, s.score:输出的数据的地址。

ret = fscanf(fp, "%d%s%lf", &temp.num, temp.name, &temp.score);
 if(EOF == ret) //到达文件结尾,EOF可以视为-1
        {
             break;
        }
        fp :指针指向的文件;

               "%d%s%lf":输入的数据格式;

               &temp.num, temp.name, &temp.score:输入的数据在程序中保存的地址。

               if():判断是否输入结束。

3. 字符串读写文件

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

函数功能:以字节的方式对文件作写操作

ret = fwrite(a, sizeof(int), 5, fp);
if(ret < 5)  {perror("fwrite");return -1;}

参数说明:

            ptr:  要写的数据的起始地址

            size:  数据的单位大小

            nmemb:  个数

            stream: 文件指针

返回值(ret):  成功返回正确写进去的数据个数

                             失败小于指定的个数   

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

 函数说明:以字节的方式读取数据  将本地文件中的数据加载到内存中

ret = fread(&temp, sizeof(int), 1, fp);

if(0 == ret) //到达文件结尾

参数说明:

       ptr: 存放读数据内存的起始地址

          size:  数据的单位大小

          nmem:  读取的个数

          stream:  文件指针

 返回值: 成功返回正确读到的数据个数

                 到达文件结尾返回0

 ps: 正常情况下,文件中数据的个数是不知道的, 但文件中数据的类型是确定的。

     所以,读数据一般都是从文件头开始,一个数据一个数据的读取,到达文件尾部结束!

3. 关闭字符串文件

fclose(fp):fp是指向你要关闭文件的指针。

四、结构体

1.定义结构体:本质就是自定义一种数据类型。(type 代指类型名) 

struct  结构体名称
{
	type1  成员名1;
	type2  成员名2;
	....
	typen  成员名n;
};

可以理解成构建一个个体,其中的“type”是你描绘的个体的各方面属性。

2.定义结构体变量

struct 结构体变量   变量名

这个变量就是你定义的一个个体。

3.访问变量空间

结构体变量.成员名称:  表示结构体变量中的某个成员

注:定义没有名称的结构体是合法的, 但是其他地方没有办法使用,只能一边定义结构体,一边定义变量

4.结构体数组:和普通数组一样,作为一个数组内部包含的都是结构体变量。

5.嵌套结构体:  在一个结构体中嵌套定义其他类型的结构体成员变量.

注:1、结构体中的成员必须是已经存在
     2、如果A结构体中嵌套B类型的结构体成员变量, 应该将B结构体在A之前定义
     3、结构体变量只能访问自己的成员, 如果有嵌套结构体成员变量,那么先访问嵌套的结构体成员变量,然后再通过这个变量去访问自己的成员!!

6.结构体与函数:和变量或者指针与函数的关系一致。

7.结构体对齐原则:

1、每个成员按照自己的字节数对齐

2、结构体对齐数就是最大成员的对齐数

3、如果最后总的字节数不是结构体对齐数的整数倍, 那么加到第一个整数倍为止

4、数组按照元素的大小对齐

8.typedef :  给已经存在的数据类型取别名

 typedef    存在的数据类型名称     别名;

9.联合体:

根据最大成员分配内存, 但最后总的字节数是对齐数的整数倍

最大成员的对齐数就是联合体的对齐数

五、预处理

1.宏定义:#define 宏名 宏体

a.无参宏定义: #define   NUM   10

1、宏名尽量大写,区分变量名
2、宏定义只做简单的替换,不做任何运算
3、宏体建议添加括号
4、#undef  宏名   :强制结束宏定义的生命周期
5、宏体可以省略,表示该宏已经定义过!

b.带参宏定义:#define   S(a,b)   ((a)*(b))

1、参数不需要加类型的

2、参数表和宏名之间不能有空格

2.条件编译。

 #if  标识符
    语句块
 #endif



#if   标识符
     语句块1

 #else 
     语句块2

 #endif



#if   标识符1

语句块1
#elif    标识符2
     语句块2
#elif    标识符3
     语句块3
     .......
#else
     语句块2

满足条件()进入语句块 #endif作为结束标志,如果条件都不满足也需要执行语句,可以用#else执行语句。

这个编译方式和if语句有些相像,后面就只放最基础的版本了。

#ifdef   标识符
      语句块
   #endif

标识符定义了进入,没定义进入下一个分支。

#ifndef   标识符
      语句块
   #endif

标识符没定义进入,定义了就进入下一个分支。

3.头文件包含“.c”

1、xx.c  称为源文件, 存储具体函数实现
   xx.h  称为头文件,存储对应模块函数声明、宏定义、结构体定义、全局变量声明

2、一个模块: 包含两部分 源文件+头文件, 文件名相同, 后缀不同
3、包含标准头文件使用  <>
     包含自己写的头文件  ""

4、防止头文件重复包含:
   (1) 方法一:
       #ifndef   _头文件名大写_H
       #define   _头文件名大写_H
         语句块
       #endif
    (2)方法二:
       #pragma  once
    
5、如果头文件不在同一个目录,如果包含头文件:
   (1)方法一:
      #include   "具体路径"

   (2)方法二:
      编译时提供头文件所在的目录路径:gcc  xx.c   xx.c  -o   xx   -I  头文件目录路径

4.常用关键字

const: 防止变量被恶意修改

int const *const p = &a;//指针的指向和指针指向的内容都不能发生改变
	const int  *const p = &a;//同上

enum:枚举

1、枚举也是自定义的一种数据类型,定义一组常量, 默认从0开始, 后面值依次递增

2、也可以重新设置常量的值
3、枚举是占内存空间的, 可以使用gdb 查看:   p  /d  常量名

4、define 一次只能定义一个常量,并且要手动设置值, 不占用内存空间,无法使用gdb查看

#endif
enum   File_Error
{
	OPEN_ERROR ,
	CLOSE_ERROR,
	READ_ERROR ,
	WRITE_ERROR,
	OTHER_ERROR,
};

static:修饰静态变量

1、修饰变量:  静态变量
              静态局部变量和静态全局变量生存期: 程序开始----》程序结束
              静态局部变量作用域是定义函数内部, 静态全局变量作用域是本.c文件

2、修饰函数:  内部函数

static  void  test();

extern

1、全局变量的声明

2、外部函数的声明

extern  int g_num;  //全局变量的声明
extern void  func();

六、内存布局

a.代码区:程序被操作系统加载到内存的时候,所有的可执行代码都加载到代码区,也叫代码段,这块内存是不可以在运行期间修改的。
b.静态区:所有的全局变量以及程序中的静态变量,以及字符串都存储到静态区, C编译器对于相同的字符串会自动进行优化分配为统一的地址。
c.栈区:栈stack是一种先进后出的内存结构,所有的自动变量,函数的形参都是由编译器自动放出栈中,当一个自动变量超出其作用域时,自动从栈中弹出。
d.堆区:堆heap和栈一样,也是一种在程序运行过程中可以随时修改的内存区域,但没有栈那样先进后出的顺序。堆是一个大容器,它的容量要远远大于栈,但是在C语言中,堆内存空间的申请和释放需要手动通过代码来完成

堆空间注意事项:

1、内存越界
2、内存泄露

3、二次释放(释放完指针仍然指向原来的堆空间,推荐释放完后指针指向NULL)

你可能感兴趣的:(c语言,数据结构)