黑马程序员—小知识点总结和结构体

时间:2023-02-17 09:16:00

——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-
一、小知识点总结:
1、fgets( ) :该函数是一个文件操作相关的函数
使用这个函数可以从键盘上接收一个字符串,保存到数组中
原来接收字符串保存到数组中的方法: char str[50]
1) scanf(“%s”,str); //缺点:不能接收空格
2)gets(str); //优点:可以接收空格
//缺点:如果输入的字符串长度大于数组长度会溢出,是不安全的
3)fget( )是一个安全的字符串接收的函数:如果使用此函数,则只会接收数组长度的n-1个元素,最后一位自动补\0,所以是非常安全的。缺点是不能格式化输出字符串。
4)如果输入长度小于数组长度时,可以用 if(char[strlen(ch)-1]==’\n’)语句提前结束输入。
5)fputs( )是不会自动换行的,也不能进行格式化输出。puts可以自动换行。

2、const:是一个类型修饰符。使用const修饰变量则可以让变量的值不能改变。
1)优点:提高了效率。编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
2)修饰一般常量:一般常量是指简单类型的常量,这种常量在定义时,修饰符const可以用在类型说明符前,也可以在其后。 如: int const x=2; 或 const int x=2;
3)修饰指针变量:1>const修饰的指针变量指向可变,指向的变量的值不可变: const int *p=& a; 或 int const *p=& a; 之后 p=& b; //可以改变 *p=1000; //不可以改变
2> const修饰的指针变量的值可以变,指向不能变: int * const p2=&a ; *p2=2000; //可以改变 p2=&b ; //不能改变
3>const修饰的指针变量的指向和值,都不能改变: const int * const p3=&a ;

3、内存管理的基本概念:是指换件运行时对计算机内存资源发分配和使用的技术。其最主要的目的是如何高效、快速的分配,并且在适当的时候释放和回收内存资源。
1)内存的分配方式有三种:
1>从静态存储区域分配。例如:全局变量,static变量。
2>在栈上创建。在函数执行时,临时创建的变量,函数执行结束后,自动被释放。
3>从堆上分配。亦称动态内存分配。程序在运行的时候用malloc或now申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
2)内存分区:黑马程序员—小知识点总结和结构体
简单介绍:1>BSS段:存放未初始化的全局变量和静态变量。
2>数据段:通常是指用来存放已初始化的全局变量和静态变量。可以分为只读数据和读写数据段。如字符串常量一般都是存放在只读数据段中。
3>代码段:通常是指用来存放程序执行代码的一块内存区域。
4>堆(heap):用于存放进程运行中被动态分配的内存段。
5>栈(stack):栈又称堆栈,是用户存放程序临时创建的局部变量,也就是我们函数括弧“{ }”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出的特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。

4、常见动态分配内存的函数:C语言中提供了三个函数:malloc , calloc和realloc。
1)malloc的使用方法: void *malloc (unsigned size) , 其中size是指分配内存的字节。作用是在内存的堆区分配一个大小为size的连续空间,如果分配内存成功,函数返回新分配内存的首地址,否则,返回NULL。
2)calloc的使用方法: 格式:calloc (块数,长度)
如:int p= (int ) calloc(4,sizeof(int)); //16个字节,且地址也是连续的
3)realloc的使用方法: 可以给已经存在的空间扩充大小。
如: int p=(int )malloc(4 *sizeof(int)); //16个字节
p=realloc(p, 40*sizeof(int)); //扩充为40个字节的内存空间

5、使用动态内存分配后容易造成内存泄漏的问题。
解决办法:在p被释放之前,先要释放堆区中的内存空间。
free函数的使用: 定义函数: void free(void *ptr);
函数说明:参数ptr为指向先前由malloc( )、calloc( )或realloc( )所返回的内存指针。调用free( )后ptr所指的内存空间便会被收回。如参数ptr所指内存空间已经被收回,再调用此函数可能会有无法预测的情况发生。
注意:当使用free(p)或free(要释放的空间的首地址)之后,p是一个野指针,所以必须要重新赋值,即 p=NULL; 这样p又可以重新使用了。

6、指针函数:一个函数的返回值是一个指针(即地址),这种返回指针值的函数称为指针型函数
指针函数的定义:一般形式为: 类型说明符 *函数名(形参表){ 函数体 return 地址}
其中函数名之前加了”*“号表明这是一个指针型函数,即返回值是一个指针。类型说明符表示了返回的指针值所指向的数据类型。 return返回值为地址。

7、函数指针:把函数的首地址(或入口地址)赋予一个指针变量,使该指针变量指向该函数。把这种指向函数的指针变量称为”函数指针变量“。
函数指针的定义:一般形式为: 类型说明符 (*指针变量名)( ):
其中”类型说明符“表示被指函数的返回值的类型。
”(指针变量名)“表示 ”“后面的变量是定义的指针变量

         int x= 3, y= 5;
int s= sum(x,y);
int (*p)(int a,int b); //定义函数指针
p = sum; //p=sum函数的首地址
//注意:定义函数指针的时候,可以不用写形参名
int (*p1)(int , int );
p1 = sum;
return 0;

8、有关指针的数据类型的小结:
1)int i; —–——> 定义整型变量
2)int *p; ———>p为指向整型数据的指针变量
3)int a[n]; —— > 定义整型数组a,它有n个元素
4)int *p[n]; ——> 定义指针数组p,它由n个指向整型数据的指针元素组成
5)int (*p)[n]; ——>p为指向含n个元素的一维数组的指针变量
6)int f( ); —-——> f为返回整型函数值的函数
7)int *p( );———> p为返回一个指针的函数,该指针指向整型数据
8)int (*p)( ); -——> p为指向函数的指针,该函数返回一个整型值
9)int **p; —–——>p是一个指针变量,它指向一个指向整型数据的指针变量
10)p1=p2; –——> p1和p2都是指针变量,将p2的值赋给p1。但p1=1000错误

二、1、结构体:构造数据类型:是根据已定义的一个或多个数据类型用构造的方法来定义的。
一个构造类型的值可以分解成若干个“成员”或“元素”。每个“成员”都是一个基本数据类型或又是一个构造类型。
1)在C语言中,构造类型有以下几种:数组类型、结构体类型、共用体(联合)类型。
2)定义结构体的方法:一般形式为:
struct 结构名{
成员表列
} ; (分号不能省略)
3)先声明结构体类型再定义变量名、在声明类型的同时定义变量或直接定义结构体类型变量 定义结构体变量的格式 : struct 结构体名 结构体变量名1,结构体变量名2 ;如:struct Student student1, student2 ;
4)引用结构体变量成员的方法: 结构体变量名.成员名 如:student1.num=100;
注意:char 类型的结构体变量成员不可以直接赋值,应为:strcpy(student1.name,”wangwei”);
但如果写成 char *name,则 name=”wangwei”是可以的。
5)初始化:可以全部初始化也可以部分初始化。可以定义的同时,也可以先定义后初始化。

2、结构数组的概念:结构数组的每一个元素都是具有相同结构类型的下标结构变量。
用来存放大量结构体相同的结构体变量。
1)结构数组定义格式:
struct 结构名{
成员表列
}数组名[数组长度] ; (分号不能省略)
1>定义结构体的同时,定义数组:
struct Student{
int age;
char *name;
float score;
}stu[5];
2>先定义结构体,后定义数组: struct Student boy[5];
2)结构体数组的初始化方法:
1>定义结构体数组的时候,进行初始化:
struct Student{
char name[20];
int age;
}boys[3]={{“sdw”,18},{“zbe”,25},{“cgl”,35}};
2>定义的同时进行初始化:
struct Student girls[2]={{”lm”,19},{“cjw”,22}};
3>先定义后初始化,整体赋值:
struct Student ds[2];
ds[0]=(struct Student){“wjo”,32};
ds[1]=(struct Student){“shd”,24};
4>先定义结构体数组,后初始化:
struct Student stu[2];
strcpy (stu[0].name,”lily”); 或 scanf(“%s”,stu[0].name,”lily”);
stu[0].age=10
3)结构数组的遍历:
已经定义和初始化完成了stu[len]的结构数组了,之后进行遍历:
for(i=0; i<len; i++)
printf("%s,%s",stu.name[i], stu.age[i]);

3、指向结构体变量的指针:一个指针变量当用来指向一个结构变量时,称之为结构指针变量。结构指针变量中的值是所指向结构变量的首地址。
1)结构指针变量说明的一般形式为:struct 结构名 *结构指针变量名
如:struct stu *pstu //定义了指针只能指向stu结构体类型的结构体变量
2)指向结构体变量的指针,其访问的一般形式为:
(*结构指针变量). 成员名 或 结构指针变量->成员名
例如: (*pstu).num 或 pstu->num
3)结构体嵌套:结构体定义的里面有其它结构体。
结构体不可以嵌套自己变量,可以嵌套指向自己这种类型的指针
结构体定义中可以嵌套其它结构体类型的变量,不可以嵌套自己这个类型的变量。
如: Struct Date{
int month;
int day;
int year;
} ;
struct stu{
int num;
char *name;
char sex;
struct Date birthday; //struct 类型不同,所以可以嵌套使用
struct *stu; //指针类型的自己类型的结构体变量可以使用
Float score;
} ;

4、结构体变量成员值做函数的参数:结构体成员属性作为函数的参数就是值传递。
用结构体变量作为函数的参数实质上还是值传递。

5、用结构体变量的地址传递给函数,也可以理解为用结构体指针作为函数的参数。
如: xiuche2(&car1); 实质是地址传递。如图:
黑马程序员—小知识点总结和结构体

三、小知识点总结:
1、枚举类型:C语言提供了一种称为“枚举”的类型,在“枚举”类型的定义中列举出所有可能的取值,被说明为该“枚举”类型的变量取值不能超过定义的范围。
枚举类型是和种基本数据类型,而不是一种构造类型,因为它不能再分解为任何基本类型。
1)枚举类型定义的一般形式为: enum 枚举类型名 { 枚举值表 }
在枚举值表中应罗列出所有可用值,这些值也称为枚举元素。
例如:一个枚举名为weekday,枚举值共有7个,即一周中的七天。凡被说明为weekday类型变量的取值只能是七天中的某一天。
2)枚举类型的变量的定义:
先定义枚举类型:enum weekday{1,2,3,4,5,6,7};
再定义枚举变量:enum weekday day;
或是同时定义:enum weekday{1,2,3,4,5,6,7} day;
3)枚举类型的初始化:枚举类型变量=枚举类型其中的某一个值 (只能是其中的某一个元素的值)
注意:枚举类型定义完成以后,系统会自动给枚举的每个元素都会赋值一个整形的初值。
默认初值:从第一个元素开始值为0,以后每个元素的值,是上一个元素的值+1。

2、typedef函数:C语言允许用户自己定义类型说明符,也就是说允许由用户 **为数据类型取“别名”。**typedef定义的一般形式为: typede 原类型名 新类型名;
其中原类型名中含有定义部分,新类型名一般用大写表示,以便于区别。

有时也可用宏定义来代替typedef的功能,但是宏定义是由预处理完成的,而typedef则是在编译时完成的,后者更为灵活方便。

3、预处理的基本概念:以“#”号开头的预处理命令。如包含命令#include,宏定义命令#define等。在源程序中这些命令都放在函数之外,而且一般都放在源文件的前面,它们称为预处理部分。

所谓预处理是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所作的工作。预处理是C语言的一个重要功能,它由预处理程序负责完成。当对一个源文件进行编译时,系统将自动引用预处理程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。

C语言提供了多种预处理功能,如宏定义、文件包含、条件编译等。合理地使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。

4、宏的概念:被定义为“宏”的标识符称为“宏名”。在编译预处理时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换,这称为“宏代换”或“宏展开”。

宏定义是由源程序中的宏定义命令完成的。宏代换是由预处理程序自动完成的。在C语言中,“宏”分为有参数和无参数两种。 宏名一般都大写。
1)无参宏的宏名后不带参数。定义形式为: #define 标识符 字符串
其中的“#”表示这是一条预处理命令。凡是以“#”开头的均为预处理命令。“define”为宏定义命令。”标识符“为所定义的宏名。”字符串“可以是常数、表达式、格式串等。
2)宏使用的注意事项:
1>宏是有作用域的 #undef 宏名 可以取当消宏定义
2>在字符串中出现的不会被替换
3>宏可以嵌套定义

3)有参宏的定义方法:对带参数的宏,在调用中,不仅要宏展开,而且要用实参去代换形参。
带参宏定义的一般形式为: #define 宏名(形参表) 字符串
有参宏使用的注意事项:
1>宏的形参之间可以出现空格,但是宏名和形参之间不能出现空格。
如: M(a, b+1)
2>有参宏的参数最好用括号括起来。 如: result =M(a+3, a-1);
3>应用:定义一个有参宏,求两个数的最大值:

#include <stdio.h>
#define Max(a,b) a>b?a:b //宏定义后面不能加分号(;)
int main( ){
int m = Max(34,88);
printf("%d\n",m);
return 0;
}

5、typedef和#define的区别:宏定义只是简单的字符串代换,是在预处理完成的,而typedef是在编译时处理的,它不是作简单的代换,而是对类型说明符重新命名。被命名的标识符具有类型定义说明的功能。
如: #define PIN1 int * //宏定义——> typedef int * PIN2 //重新定义类型

6、条件编译指令:#if_#else:
1)发生在预处理阶段,在编译之前做的事情。
核心:根据条件编译指定的代码
条件不同,编译的部分也不同,生成的目标文件(.o) 的大小也不同
2)如何使用:例如检测学生的成绩并且输出等级:
#if score < 60
printf(“E\n”);
#elif score<=69
printf(“D\n”);
#elif score<=79
printf(“C\n”);
#elif score<=89
printf(“B\n”);
#else
printf(“A\n”);
#endif

7、条件编译指令:1)#ifdef格式为:# ifdef 标识符 ——程序段1——#else——程序段2——#endif
它的功能是,如果标识符已被#define命令定义过则对程序段1进行编译;否则对程序段2编译。
2)#ifndef格式也同样为:# ifndef 标识符 ——程序段1——#else——程序段2——#endif
它的功能是,如果标识符没有被#define命令定义过则对程序段1进行编译;否则对程序段2编译。

8、使用条件编译指令调试bug:

#include <stdio.h>
#define DEBUG1 1
#if DEBUG1 == 1
//显示调试信息
#define Log(format,...) printf(format, ## __VA__ARGS__);

#else //不显示调试信息
#define Log(format,...)

#endif

int main( ){
//DEBUG1==1 显示调试信息
//DEBUG1==0 不显示高度信息
Log("xxxxxxxxxx-->%d\n",10);
return 0;
}