黑马程序员--C语言自学笔记---12fgets、fputs、const、结构体

来源:互联网 发布:java 整型数组 编辑:程序博客网 时间:2024/05/20 04:30

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

1.   fgets()函数的使用

1)fgets的原型为char * fgets(char *s,int n ,FILE  * fp);

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

2)fgets是一个文件操作相关的函数

暂时使用这个函数可以从键盘接收一个字符串,保存到数组中。

3)其他接收字符串保存到数组中的方法

Char str[50];

①  scanf(“%s”,str);  缺点:不能接收空格

②  gets(str);       优点:可以接收空格

缺点:会有一个警告,不安全。

不安全是指比如数组长度是50,如果输入的长度正好是50个,此时会把50个字符全部存到数组中,存在问题,因为没有空间存放字符串结束符’\0’

4)而fgets()是一个安全的字符串接收函数

char ch[5];                   如果使用fgets,此时数组中最多存放4个可见字符,会自动的把数组的最后一个元素存放’\0’

fgets()使用格式        fgets(数组名,数组长度,stdin)stdin表示从输入缓冲区中读取字符串存放到数组中,比如:charch[5];fgets(ch,sizeof(ch),stdin);

①          输入的字符串的长度大于数组的长度,fgets会自动把数组的最后一个元素变成\0。

②          当输入的字符串的长度小于数组长度时,fgets会把回车符接收到数组中。

2.   fputs()函数的使用

1)        也是一个与文件操作有关的函数

格式为:fputs(数组名,stdout),比如:

char ch[5]={“abcd”};fputs(ch,stdout);

2)        fputs()不会自动换行,也不能进行格式化的输出,而puts()可以自动换行

3.   const关键字

1)        const是一个类型修饰符,使用const修饰变量可以让变量的值不能改变,也就是说,用const修饰的变量会变成一个常量

一个用指针强行修改const变量的无解问题:

const int a = 5;

int *p = &a;

*p = 10;

然后执行printf(“a=%d,*p%d\n”,a,*p);

结果为a=5,*p=10;

2)        可以修饰指针变量

注意:以下所说的指针变量指向的值能不能改变的意思是说,能不能通过这个指针来改变他所指向的变量的值,但是这个变量还是可以改变的。即:

只跟是否可以通过对*p赋值来改变该变量的值有关,而对a都是可以赋值的,因为a还是一个变量,可以随便来赋值。

int a=5;

int b=10;

const int *p=&a;

*p=1;这时错误的

p=&b;这是正确的

a=1;这是正确的

并不是把指向的变量变为常量。

 

①  第一种,表示指针变量指向的值不能改变,但指向可以改变:

 

int a=5;

int b=10;

const int *p=&a;

*p=1;这时错误的

p=&b;这是正确的

②  第二种,表示指针变量指向的变量的值可以改变,但指向不能改变

int a=5;

int b=10;

int * const  p=&a;

*p=1;这时正确的

p=&b;这是错误的

 

③  第三种,表示指针变量指向的变量和它的指向都不能改变

int a=5;

int b=10;

const int * const  p=&a;

*p=1;这时错误的

p=&b;这也是错误的

 

3)        可以修饰数组

4.   内存管理

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

内存分配方式

1)        从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。

2)        在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时,这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

3)        在堆上分配,动态分配内存。程序在运行的时候用malloc或new申请人以多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也很多。

内存分区,地址从高到低为:栈区,堆区,BSS段,数据段,代码段


1)        BSS段,用来存放程序中未初始化的全局变量和静态变量

2)        数据段,存放的是已经初始化的全局变量和静态变量,也成为常量区。

3)        代码段,存放程序执行代码的一块内存区域。

4)        堆,存放被动态分配的内存段

5)        栈,用户存放程序临时创建的局部变量

5.   内存分配函数

1)        malloc(头文件stdlib.h)

使用方法为void*malloc(unsigned size),size是指分配内存的字节数。

从内存中的堆区分配大小为size字节的连续内存空间,如果内存分配成功,返回内存的首地址;失败的话,会返回NULL;

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

if(p!=NULL){

*p = 10;

*(p+1) = 11;

*(p+2) = 12;

*(p+3) = 13;

}else{

printf(“申请失败!\n”);

}

注意:如果malloc分配的内存不初始化的话,存放的是垃圾数,可以使用memset()函数来进行初始化。

Memset(p,初始化的值,空间大小)

2)        calloc函数

格式为:calloc(内存块数,长度),分配指定块数和每块长度的空间,地址也是连续的,并且会自动初始化为0.

calloc(4,sizeof(int));分配了4块空间,每块长度都是sizeof(int),

3)        realloc函数

可以给已经存在的空间进行扩展大小

int *p=malloc(4*sizeof(int)); 假设首地址为0x1000

*p=1;*(p+1)=2;*(p+2)=3;*(p+3)=4;

p = realloc(p,40*sizeof(int));

分配原理:以上面这个为例,首先realloc会在原有空间后面(0x1016)查看是否有足够的空间(36*sizeof(int))可以继续分配,如果有则直接进行扩展;如果没有,则会在内存中其他地方分配足够大的空间(40*sizeof(int)),所以地址会改变,需要重新用P来接收,而且,原有内存空间的数据会复制到新空间的开始位置。*p,*(p+1),*(p+2),*(p+3)的值依然是1,2,3,4.

6.   野指针和内存泄露

1)        如果动态申请内存成功后,使用完后不主动进行释放,当程序执行完,指向该内存空间的首地址的指针已经被释放,而该空间未被释放,就会造成该部分空间无法访问,造成内存泄露。如果申请的内存空间先被释放,而指针还未被释放,则指针变为野指针。

2)        使用free函数来释放内存空间。

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

*p=1;

free(p);

p=NULL;

free(内存的首地址);释放内存空间,防止内存泄露这里为free(p),此时p变为野指针。

执行p=NULL;重新把野指针赋值为0,让其不再是野指针

7.   指针函数

返回指针值的函数称为指针函数

1)        类型说明符 * 函数名(形参表){函数体}比如:

int *sum(int a,int b){int sum;sum = a+b;return  ∑}


2)        用来求两个数的大数的地址

int *max(int x,int y){return *x>*y?x:y;}

3)        定义指针函数根据输入的数字,输出对应的星期

char * getDay(int n){char *days[]={“星期一”,“星期二”,“星期三”,“星期四”,“星期五”,“星期六”,“星期日”};return 0<n&&n<8?days[n-1]:”输入错误”;}

4)       用指针做参数,求最大值

//用指针做形参求最大值int max(int *p,int len){int max=*p;for (int i=1; i<len; i++) {if (*(p+i)>max) {max = *(p+i);       }    }return max;}

8.   函数指针

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

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

2)        定义:函数的声明为int sum(int a,int b);

----->函数指针定义为:int  (*p)(int a,int b)

定义了一个函数指针p,p可以存放 返回值是int类型,并且有两个形参,形参的类型还都是int类型的函数的地址

3)        初始化

p=sum; sum中存放的就是sum函数在内存中的首地址

4)        定义函数指针时,可以省略函数形参名int (*p)(int ,int),类型不能省

5)        函数指针使用

假设有两个结构相同的函数为intsum(int a,int b)和int sub(int a,intb),定义一个函数指针为int(*p)(int ,int),可以通过修改一个分支语句来调用不同的函数:如下

switch(1){

case 1:

p=sum;

break;

case 2:

p=sub;

break;

}

int rs = p(3,1);

只要改变switch中的数字,就可以调用不同的函数,这就是函数指针的使用之处。

个人观点:感觉实际应用中这种写法没多大必要,想调用不同函数只要使用相应的函数名就可以了,使用函数指针的话,反而增加了程序的代码量

6)        注意:

①  函数指针变量不能进行算数运算,这跟数组指针变量不一样,数组指针变量加减运算可以让指针移动到某个数组元素前面,函数指针的移动没有任何意义。

②  函数调用时,可以写为p(1,2)格式,也可以写为(*p)(1,2)格式,但第二种格式的括号不能少,这个*不应该理解为求值运算,只是一种表示符号。

 

9.   构造类型

有一个或者多个已经定义类型的元素用够早的方法,构造新的类型

有:数组、结构体、联合

10. 结构体

由相同类型或者不同类型的数据用构造的方法,构造新的类型

1)        定义格式

struct结构名{

成员列表

};

比如定义一个学生结构体↓↓↓

struct Student{

int  num;

char *name;

char *sex;

};

结构体变量的三种定义方法↓↓↓

①  结构体定义完成以后,计算机并不会给结构体分配内存空间

②  会在定义结构体变量后,给结构体分配存储空间

以上面学生结构体为例定义结构体变量的方法↓↓↓

2)        先定义结构,再说明结构变量

格式为:struct 结构体名 结构体变量名;

定义结构体变量为struct Student stu1,stu2,stu3;可以看到,还能同时定义多个结构体变量。

3)        定义结构体的同时定义结构体变量

struct Student{

  成员列表

}stu4,stu5,stu6;这样便在定义结构体的同时定义了三个结构体变量。

4)        使用匿名结构体来定义结构体变量

格式为:struct{

      成员列表

}结构体变量名1,结构体变量名2,结构体变量名3…;

比如:

struct{

int  num;

char *name;

char *sex;

}stu7,stu8,stu9;

这样便用匿名的方式定义了三个结构体变量。

11. 结构体变量中成员的访问方法

通过使用’.’来访问结构体成员。

struct Student{

int  num;

char *name;

char *sex;

};

struct Student stu;

//通过使用‘.’来访问结构体变量中的成员。

stu.num = 10;

stu.name=”张三”;

stu.sex = “男”;

 

12. 结构体变量的初始化

struct Student{

      int  age;

char name[20];

};

1)        先定义结构体变量,后初始化

struct Student stu1;

stu1.age=19;

stu1.name=”zhangsan”;注意:这样初始化字符串是错误的。

这种写法就类似于char   ch[5];ch=”abcd”;因为数组名为常量,所以不能直接赋值。

对于字符串的初始化,可以使用strcpy函数

strcpy(stu1.name,”zhangsan”);这样写就没问题了。

另外,如果结构体中定义的不是数组而是char*name;则name在初始化后是可以改变的,因为它是一个指针变量。

2)        定义结构体变量的同时,初始化。

struct Student stu1={19,”张三”};

这里的字符串的初始化方式就类似于,charname[12]=”张三丰”;所以也是没问题的。

补充一个编码问题:

UTF-8 一个汉字三个字节,xcode的默认编码为UTF-8

GBK(GB2312) 一个汉字两个字节

3)        定义结构体变量的同时,只给指定元素进行初始化

struct Student stu3={.name=”张三丰”};

13. 结构体变量占用存储空间的大小

1)        求模数

2)        考虑对齐,计算大小

14. 结构体作用域

1)        全局结构体:定义在函数外部的结构体

2)        局部结构体:定义在函数内部的结构体

 

0 0
原创粉丝点击