C语言进阶-创新互联

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

成都创新互联公司服务紧随时代发展步伐,进行技术革新和技术进步,经过10多年的发展和积累,已经汇集了一批资深网站策划师、设计师、专业的网站实施团队以及高素质售后服务人员,并且完全形成了一套成熟的业务流程,能够完全依照客户要求对网站进行成都网站设计、网站建设、建设、维护、更新和改版,实现客户网站对外宣传展示的首要目的,并为客户企业品牌互联网化提供全面的解决方案。

目录

一、指针基础

二、函数

三、文件

四、结构体

五、预处理

六、内存布局


一、指针基础

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+n、p++、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 #include2 
  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.地址传参:将主函数中元素的地址传递到被使用函数中,这样可以直接通过地址改变地址所保存的数。也就是说,如果被使用函数可能需要改变主函数中参数的值,就把主函数中参数的值的地址传递过去。

例如:

#include2 
  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的值不同,为什么呢?

调用函数前

调用函数时

函数结束后

于是就出现了*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)

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


当前文章:C语言进阶-创新互联
网站网址:http://ybzwz.com/article/djppsi.html