• 问题总会出现,不过解决问题的方法也会出现!!!

C语言基础学习笔记17

学习笔记 小雨 815次浏览 已收录 0个评论

—— ——-

2-fgets()、fputs()函数使用

  • fgets()函数使用

fgets的原型是:

char*fgets(char*s,int n,FILE*fp);

参数数量比较多,有3个。而fgets相比于gets有一个显著的差别就是fgets会将行末的换行符算到读入的字符串里面。所以相同且正常(输入无错误,缓冲区够大)的情况下,fgets读入的字符串会比gets在末尾“”前面多一行符;行长度超出缓冲区大小时只读入前n-1个字符。

因此:gets(s)相当于fgets(s,sizeof(s),stdin);

C语言基础学习笔记17

C语言基础学习笔记17C语言基础学习笔记17

C语言基础学习笔记17

C语言基础学习笔记17

3-fgets()和fputs()优缺点

  • fgets()、fputs()的优点

先来看看使用其他的函数的缺点

  • 使用scanf()接收一个大于字符数组长度的字符串

C语言基础学习笔记17

  • 使用gets接收一个大于字符数组长度的字符串

C语言基础学习笔记17

fgets()、fputs()最大的优点是帮我们自动截取输入的字符串,使得我们对字符串的存取是安全的。

4-const关键字介绍

  • 什么是const?

const是一个类型修饰符

使用const修饰符变量则可以让变量的值不能改变

常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被更新的。

  • const有什么主要的作用?

  • 可以定义const常量,具有不可变性。例如:

const int Max=100;int Array[Max];

  • 便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患。

例如:void f(const int i){……}编译器就会知道i是一个常量,不允许更改;

  • 可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改。同宏定义一样,可以做到不变则已,一变都变!如(1)中,如果想修改Max的内容,只需要:const int Max=you want;即可!
  • 可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。还是上面的例子,如果在函数体内修改了i,编译器就会报错:

例如:void f(const int i){i=10;//error!}

  • 可以节省空间,避免不必要的内存分配。例如:

#define PI 3.14159;//常量宏

const double Pi=3.14159;//此时并未将Pi放入ROM中

double i=Pi;//此时Pi分配内存,以后不再分配!

double I=PI;//编译期间进行宏替换,分配内存

double j=Pi;//没有内存分配

double J=Pi;//再进行宏替换,又一次分配内存!

const定义常量从汇编的角度看,只是给出了对应的内存地址,而不是像#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。

  • 提高了效率。编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
  • 如何使用const?

  • 修饰一般常量,一般常量是指简单类型的常量。这种常量在定义时,修饰const可以用在类型说明符前,也可以用在类型说明符后。

例如:int const x=2;或const int x=2;

(当然,我们可以偷梁换柱进行更新:通过强制类型转换,将地址赋给变量,再作修改既可以改变const常量值)

C语言基础学习笔记17

  • 修饰常量组(值不能够再改变了)定义或说明一个常数组可以采用如下格式:

int const a[5]={1,2,3,4,5};

const int a[5]={1,2,3,4,5};

C语言基础学习笔记17

  • 修饰函数的常参数,const修饰符也可以修饰函数的传递参数,格式如下:

void Fun(const int Var);告诉编译器Var在函数体中的无法改变,从而防止了使用者的一些无意的或错误的修改。

  • 修饰函数的返回值:const修饰符也可以修饰函数的返回值,是返回值不可被改变,格式如下:

const int Fun1();const MyClass Fun2();

  • 修饰常指针

C语言基础学习笔记17 C语言基础学习笔记17

6-内存管理的概念和内存分区

  • 内存管理的基本概念

内存管理,是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效,快速的分配,并且在适当的时候释放和回收内存资源。

  • 内存分配方式:内存分配方式有三种:

    • 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存的在程序的整个运行期间都存在。例如:全局变量,static变量。
    • 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集,效率很高,但是分配的内存容量有限。
    • 从堆上分配,亦称为动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
  • 内存分区

C语言基础学习笔记17

BSS段:BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量和静态变量。

BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。BSS节不存储任何数据,只是简单的维护开始和结束的地址,即总大小,以便内存区能在运行时分配并被有效地清零。BSS节在应用程序的二进制映像文件中并不存在,即不占用磁盘空间,而只在运行的时候占用内存空间,所以如果全局变量和静态变量未初始化那么其可执行文件要小很多。

数据段:数据段(data segment)通常是指用来存放程序中已初始化的全局变量和静态变量的一块内存区域。数据段属于静态内存分配,可以分为只读数据段和读写数据段。字符串常量等,但一般都是放在只读数据段中。

代码段:代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读,某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等,但一般都是放在只读数据段中。

堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中剔除(堆被缩减)

栈(stack):栈又称堆栈,是用户存放程序临时创建的局部变量,也就是我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放会栈中。由于栈的先进后出特点,所以,栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存,交换临时数据的内存区。

6-常见的内存分配函数

C语言中提供了三个动态分配内存的函数:malloc、calloc、realloc

  • malloc

它的使用方法 void *malloc(unsigned size),其中size是指的分配内存的字节

void *malloc(unsigened size),包含在库函数stdlib.h中,作用是在内存的堆区分配一个大小为size的连续空间,如果分配内存成功,函数返回新分配内存的首地址,否则,返回NULL,注意:鉴于上述这点,一般写程序需要判断分配内存是否成功。

如下程序语句:

int *p;

p=(int *)malloc(sizeof(int));

if(p!=NULL)…..//需要执行的语句

else……//打印分配内存不成功出错信息

malloc所分配的是一块连续的内存。

C语言基础学习笔记17

  • calloc

原型void*calloc(unsigned n,unsigned size)

或void*calloc(size_tenum_elements,size_telement_size)

n表示需要分配内存的数据项个数,size指每个数据项的大小。

返回值向内存的指针之前把它们初始化为0

C语言基础学习笔记17

C语言基础学习笔记17

  • realloc

原型: extern void *realloc(void *mem_address, unsigned int newsize);

语法

指针名=(数据类型*)realloc(要改变内存大小的指针名,新的大小)。

新的大小可大可小(但是要注意,如果新的大小小于原内存大小,可能会导致数据丢失,慎用!)

返回值

如果重新分配成功则返回指向被分配内存的指针,否则返回空指针NULL。

注意

当内存不再使用时,应使用free()函数将内存块释放。

7-野指针和内存泄露

      • 野指针

(1)指针变量未初始化

任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。

(2)指针释放后之后未置空

有时指针在free或delete后未赋值 NULL,便会使人以为是合法的。别看free和delete的名字(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。此时指针指向的就是“垃圾”内存。释放后的指针应立即将指针置为NULL,防止产生“野指针”。

      • 内存泄露

由程序申请的一块内存,如果没有任何一个指针指向它,那么这块内存就泄漏了。

C语言基础学习笔记17

free函数使用

函数说明:参数ptr为指向先前由malloc()、calloc()或realloc()所返回的内存指针。调用free()后ptr所指向的内存空间便会被收回。假若参数ptr所指的空间已经被收回或是未知的内存地址,则调用free()可能会有无法预期的情况发生。若参数ptr为NULL,则free()不会有任何作用。

其实,free函数只是将参数指针指向的内存归还给操作系统,并不会把参数指针置NULL,为了以后访问到被操作系统重新分配后的错误数据,所以在调用free之后,通常需要手动将指针置NULL。从另一个角度看,内存这种底层资源都是由系统来管理的,而不是编译器,编译器只是向操作系统提出申请,所以free函数是没有能力去真正的free内存的。只是告诉操作系统它归还了内存,然后操作系统就可以修改内存分配表,以供下次分配。

注意:free(p)后,一定要再把p赋值NULL,

free(p);

p=NULL;

否则还能继续访问指针

C语言基础学习笔记17

8-指针函数概念及定义

  • 指针函数概念

C语言中允许一个函数的返回值是一个指针(即地址),这种返回指针值的函数称为指针型函数。

  • 指针函数的定义

定义指针型函数的一般形式为:

类型说明符 *函数名(形参表){

函数体

}

其中函数名之前加了“*”号表明这是一个指针型函数,即返回值是一个指针。类型说明符表示返回的指针值所指向的数据类型。

10-函数指针概念及定义

  • 函数指针

C语言中,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址。

我们可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使该指针变量指向该函数。然后通过指针变量就可以找到并调用这个函数。我们把这种指向函数的指针变量称为“函数指针变量”。

  • 函数指针定义方法

函数指针变量定义的一般形式:

类型说明符 (*指针变量名)();

其中“类型说明符”表示被指函数的返回值的类型。

“(*指针变量名)”表示“*”后面的变量是定义的指针变量

最后的空括号表示指针变量所指的是一个函数。

11-函数指针使用

  • 利用函数指针调用函数

调用函数的一般形式为:

(*指针变量名)(实参表)

C语言基础学习笔记17

从上述程序可以看出用函数指针变量形式调用函数的步骤如下:

  • 先定义函数指针变量,如int(*fun)(int *p,int len);定义fun为函数指针变量
  • 把被调用函数的入口地址(函数名)赋予该函数指针变量,如程序中fun=getMax;
  • 用函数指针变量形式调用函数,如程序int max=fun(a,5);

使用函数指针变量还应注意以下两点:

  • 函数指针变量不能进行算术运算,这是与数组指针变量不同的。数组指针变量加减一个整数可使指针移动指向后面或前面的数组元素,而函数指针的移动是毫无意义的。
  • 函数调用“(*指针变量名)”的两遍的括号不可少,其中的*不应该理解为求值运算,在此处它只是一种表示符号。

12-构造类型及结构体

构造数据类型:构造数据类型是根据已定义的一个或多个数据类型用构造的方法来定义的。也就是说,一个构造类型的值可以分解成若干个“成员”或“元素”。每个“成员”都是一个基本数据类型或又是一个构造类型。

C语言中,构造类型有以下几种:

数组类型

结构体类型

共用体(联合)类型

  • 是什么结构体?

“结构”是一种构造类型,它是由若干“成员”组成的。每一个成员可以是一个基本数据类型或者又是一个构造类型。结构既是一种“构造”而成的数据类型,那么在说明和使用之前必须先定义它,也就是构造它。如同在说明和调用函数之前要先定义函数一样。

  • 为什么要有结构类型?

结构体可以把类型相同的数据组织起来,存在一起,用的时候方便,而且在调用函数时,若传递参数较多,传一个结构体相对而言简单一些,很多系统自带的函数必须用结构体。

13-定义结构体的方法

定义一个结构的一般形式为:

struct 结构体{

成员列表

};

//定义一个学生的结构

struct stu{

int num;

char name[20];//char *name;

char sex;

float score;

};//此处分号;不能省略

成员表列由若干个成员组成,每个成员都是该结构的一个组成部分。对每个成员也必须作类型说明,其形式为:

类型说明符 成员名;

14-结构体变量及定义方法

  • 先定义结构,再说明结构变量。

struct stu{

int num;

char name[20];

char sex;

float score;

};

struct stu boy1,boy2;

C语言基础学习笔记17

也可以用宏定义使一个符号常量来表示一个结构类型。

#define STU struct stu

STU{

int num;

char name[20];

char sex;

float score;

};

STU boy1,boy2;

  • 在定义结构类型的同时说明结构变量。

struct stu{

int num;

char name[20];

char sex;

float score;

}boy1,boy2;

这种形式的说明的一般形式为:

struct 结构名{

成员表列

}变量名列表;

  • 匿名结构体定义结构体变量。

struct{

int num;

char name[20];

char sex;

float score;

}boy1,boy2;

这种形式的说明的一般形式为:

struct{

成员列表

}变量名表列;

第三种方法与第二种方法的区别第三种方法中省去了结构名,而直接给出结构变量,这种结构体最大的问题是,不能再次定义新的结构体变量了。

C语言基础学习笔记17

15-结构体变量中成员的访问方法

在程序中使用机构变量时,往往不把它作为一个整体来使用。在ANSI C中除了允许具有相同类型的结构变量相互赋值以外,一般对结构变量的使用,包括赋值、输入、输出、运算等都是通过结构变量的成员来实现的。

表示结构变量成员的一般形式是:

结构体变量名.成员名

例如:boy1.num 即第一个人的学号

boy2.sex 即第二个人的性别

如果成员本身又是一个结构则必须逐级找到最低级的成员才能使用。

例如:boy1.birthday.month

即第一个人出生的月份成员可以在程序中单独使用,与普通变量完全相同。

16-结构体变量的初始化

  • 先定义结构体变量,然后再初始化

stuct stu{

int num;

char name[20];

char sex;

float score;

};

struct stu boy1,boy2;

boy1.num=1010;

strcpy(boy1.name,”bbbbb”);

//boy1.name=”clik”;//这是错误的写法

//相当于char name[20];

//如果写成char *name,则name=“abc”可以;

  • 定义的同时初始化

  • 全部初始化

struct stu{

int num;

char name[20];

char sex;

float score;

}av1={1012,”xzzzz”,’f’,30};

//注意“zxxxx”初始化赋值给了name[20]

printf(”av1.name = %s”,av1.name);

初始化的顺序必须与结构体定义的时候成员的顺序一致。

struct stu boy1={1011,”zzz”,’f’,23};

使用另外一已经存在的结构体初始化新的结构体

C语言基础学习笔记17

注意:两个相同类型的结构体变量的赋值,相当于普通变量的赋值,是整体拷贝,而不是地址赋值。

C语言基础学习笔记17

  • 部分初始化

struct stu boy1={.name=”weiwei”};

17-结构体变量存储原理

  • 结构体存储原理

结构名只能表示一个结构形式,编译系统并不对它分配内存空间。只有当某变量被说明为这种类型的结构时,才对该变量分配存储空间。

内存是以字节为单位编号,但一些硬件平台对某些特定类型的数据只能从某些特定地址开始,比如从偶地址开始。若不按照适合其平台的要求对数据存放进行对齐,会影响到效率。

因此,在内存中,各类型的数据是按照一定的规则在内存中存放的,这就是对齐问题。

结构体占用的内存空间是每个成员占用的字节数之和(考虑对齐问题)。

  • 结构体数据成员对齐的意义

许多实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的起始地址的值是某个数K的倍数,这就是所谓的内存对齐,而这个K则称为该数据类型的对齐模数(alignment modulus)。

这种强制的要求一来简化了处理器与内存之间传输系统的设计,二来可以提升读取数据的速度。比如这么一种处理器,它每次读写内存的时候都从某个8倍数的地址开始,一次读出8个字节的数据,加入软件能保证double类型的数据都从8倍数据地址开始,那么读或写一个double类型数据就只需要一次内存操作。否则,我们就可能需要两次内存操作才能完成这个动作,因为数据或许恰好横跨在两个符合对齐要求的8字节内存块上。

  • 结构体对齐的含义
  • 结构体总长度
  • 结构体内各数据成员的内存对齐,即该数据成员相对结构体的起始位置

18-结构体变量占用存储空间大小

  • 结构体大小的计算方法和步骤
  • 将结构体内所有数据成员的长度值相加,记为sum_a;
  • 将各数据成员为了内存对齐,按各自对齐模数而填充的字节数累加到和sum_a上,记为sum_b。对齐模数是#pragma pack指定的数值以及该数据成员自身长度中数值较小者。该数据相对起始位置应该是对齐模数的整数倍;
  • 将和sum_b向结构体模数对齐,该模数是#pragma pack指定的数值和结构体内部最大的基数数据类型成员长度中数值较小者。结构体的长度应该是该模数的整数倍。

所谓“对齐在N上”,指定是“存放起始地址%N=0”

struct A{

int a;         //4

char b;      //分配4个,用了1个,剩下3个

short c;    //需要2个

};

printf(”%zdn”,sizeof(struct A));

19-结构体作用域

  • 作用域概述

结构类型定义在函数内部的作用域与局部变量的作用域是相同的

函数外部定义的结构体类型类似全局变量

全局作用域:从定义的那一行开始直到本文件结束为止

  • 作用域分类

结构体根据作用于可以分为全局结构体、局部结构体


本博客内容既有转载自网络的内容,也有本作者原创内容,仅供学习与交流之用
如有侵权或者错误之处,请及时在下方留言!
喜欢 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址