c语言学习第五天笔记

 结构体struct:
1、一般形式为:
struct 结构体名{
结构体所包含的成员变量
}
例如:struct stu {
char name[20];
int num;
int age;
char group;
float score;
};
需要注意,‘;’不能丢
2、定义结构体类型变量的方法:
struct stu{...}s1,s2;
struct {...}s1,s2;
在main函数中定义 struct stu s1,s2;
3、变量的赋值:
struct stu s1={"tom",12,18,'a',136.5};
struct stu s1={.name="tom",.num=12......};
sturct stu{...}s1={"tom",12,18.....};
访问结构体成员变量使用点号操作符.
s1.name="tom";
不能将一个结构体变量作为一个整体进行输入和输出。
4、内存对齐:
默认对齐系数是4,内存对齐是操作系统为了快速访问内存而采取的一种策略。操作系统在访问内存 时,每次读取一定的长度(这个长度就是操作系统的默认对齐系数,或者是默认对齐系数的整数倍)。如果没有内存对齐时,为了读取一个变量是,会产生总线的二次访问。
5、位域:
在结构体定义时,可以指定某个成员变量所占用的二进制位数
struct bs{
unsigned m : 2;
unsigned n : 4;
unsigned char ch: 6;
};
   当相邻成员的类型相同时,如果它们的位宽之和小于类型的 sizeof 大小,那么后面的成员紧邻前一个成员存储,直到不能容纳为止;如果它们的位宽之和大于类型的 sizeof 大小,那么后面的成员将从新的存储单元开始,其偏移量为类型大小的整数倍。
   一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。
   位域成员可以没有名称,只给出数据类型和位宽,无名位域一般用来作填充或者调整成员位置。因为没有名称,无名位域不能使用。
6、结构体数组:
由相同结构体变量组成的数组,访问时要加下标,stu[0].name...
7、结构体嵌套:
一个结构体中嵌套另一个结构体,引用时逐级访问。
8、结构体指针:
struct student stu = {"xiaoming", 18, 50};
struct student *pstu = &stu;
printf ("name = %s\n", (*pstu).name);或者printf ("name = %s\n", pstu->name);
 共用体union
1、共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。
2、共用体占用的内存等于最长的成员占用的内存。共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。
3、共用体的所有成员起始地址的是一样的。
4、大小端模式:
大端模式:字数据的高字节存储在低地址中,而字数据的低字节则存放在高地址中。
小端模式:字数据的高字节存储在高地址中,而字数据的低字节则存放在低地址中。
 枚举enum
enum week{ Mon, Tues, Wed, Thurs, Fri, Sat, Sun };
枚举列表中的 Mon、Tues、Wed 这些标识符的作用范围是全局的,不能再定义与它们名字相同的变量。
Mon、Tues、Wed 等都是常量,不能对它们赋值,只能将它们的值赋给其他的变量
默认第一个元素初始值为0,后面累加
 预编译
define:
1、#define定义宏常量可以出现在代码的任何地方
2、从#define宏定义位置开始,以后的代码就都可以使用这个宏了
3、编译器会在预处理的时候用真身替换宏
4、#define STR_3  "hell\
o"
反斜杠作为接续符的时候,在本行后面不能再有任何字符,空格都不行。
5、宏撤销 #define PI 3.1415926 
 #undef PI
6、内置宏
__LINE__:表示正在编译的文件的行号
__FILE__:表示正在编译的文件的名字
__DATE__:表示编译时刻的日期字符串
__TIME__:表示编译时刻的时间字符串
__FUNCTION__:表示编译时候所在的函数名字
条件编译ifdef:
1、条件编译的功能使得我们可以按不同的条件区编译不同的程序部分,因而产生不同的目标代码。
2、#ifdef 标识符
程序段1
  #else
程序段2
  #endif
如果标识符已被#define命令定义过,或者编译的时候增加宏:gcc -D_DEBUG,则对程序段1进行编译;否则对程序段2进行编译。如果没有程序段2(它为空),本格式种的#else可以没有,既可以写为:
  #ifdef 标识符
程序段
  #endif
3、与上一种形式的区别是ifdef改为ifndef。它的功能是:如果标识符未被#define命令定义过,则对程序段1进行编译;否则对程序段2进行编译。这与第1种形式的功能正好相反
4、#if 常量表达式
程序段1
  #else
程序段2
  #endif
 
它的功能是:如果常量表达式的值为真(非0),则对程序段1进行编译;否则对程序段2进行编译。因此可以使程序在不同条件下编译,完成不同的功能。
#
#用于在与编译期间将宏参数转化为字符串
##用于在预编译期间粘连两个符号
 关键字
extern:
extern可置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量或函数时,在其它模块中寻找其定义。
static:
1、在局部静态变量前面加上关键字static,该局部变量便成了静态局部变量。静态局部变量有以下特点:
(1)该变量在全局数据区分配内存
(2)如果不显示初始化,那么将被隐式初始化为0
(3)它始终驻留在全局数据区,直到程序运行结束
(4)其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束。
(5)静态局部变量存放在内存的全局数据区。函数结束时,静态局部变量不会消失,每次该函数调用 时,也不会为其重新分配空间。它始终驻留在全局数据区,直到程序运行结束。静态局部变量的初始化与全局变量类似.如果不为其显式初始化,则C++自动为其 初始化为0。
2、在全局变量前面加上关键字static,该全局变量变成了全局静态变量。
全局静态变量有以下特点:
(1)在全局数据区内分配内存
(2)如果没有初始化,其默认值为0
(3)该变量在本文件内从定义开始到文件结束可见,即只能在本文件内使用
  3、在函数的返回类型加上static关键字,函数即被定义成静态函数。
静态函数有以下特点:
(1) 静态函数只能在本源文件中使用
(2) 在文件作用域中声明的inline函数默认为static
说明:静态函数只是一个普通的全局函数,只不过受static限制,他只能在所在文件内使用,不能在其他文件内使用。
const:
1、用来定义只读变量,具有不可变性。
2、修饰一般变量:
一般变量是指简单类型的只读变量。这种只读变量在定义时,修饰符const可以用在类型说明符前,也可以用在类型说明符后。int const i = 2;  或 const int i = 2;
3、修饰数组:
定义或说明一个只读数组可采用如下格式:
int const a[5] = {1,2,3,4,5}; 或 const int a[5] = {1,2,3,4,5}
4、修饰指针:
const int * p;             // p可变,p指向的对象不可变
int const * p;             // p可变,p指向的对象不可变
int * const p;             // p不可变,p指向的对象可变
const int * const p;       // 指针p和p指向的对象都不可变
先忽略类型名(编译器解析的时候也是忽略类型名),我们看const离哪个近,离谁近就修饰谁。
const (int) *p   //const 修饰*p,p是指针,*p是指针指向的对象,不可变。
(int) const * p; //const 修饰*p,p是指针,*p是指针指向的对象,不可变。
( int) * const p;//const 修饰p,p不可变,p指向的对象可变
const ( int)* const p;  // 前一个const修饰*p,后一个const修饰p,指针p和p指向的对象都不可变
  5、修饰函数参数:
const修饰也可以修饰函数的参数,当不希望这个参数值在函数体内被意外改变时使用,例如:
    void Fun(const int *p);
告诉编译器*p在函数体中不能改变,从而防止了使用者的一些无意的或错误的修改。
6、修饰函数的返回值:
const修饰符也可以修饰函数的返回值,返回值不可被改变。例如:
    const int Fun(void);
typedef:
1、使用关键字 typedef 可以为类型起一个新的别名,语法格式为:
typedef  oldName  newName;
oldName 是类型原来的名字,newName 是类型新的名字。
需要强调的是,typedef 是赋予现有类型一个新的名字,而不是创建新的类型。为了“见名知意”,请尽量使用含义明确的标识符,并且尽量大写。
2、给数组起别名:
typedef char ARRAY20[20];
表示 ARRAY20 是类型char [20]的别名。它是一个长度为 20 的数组类型。接着可以用 ARRAY20 定义数组:
ARRAY20 a1, a2, s1, s2;
它等价于:
char a1[20], a2[20], s1[20], s2[20];
3、给结构体类型定义起别名:
typedef struct stu{
    char name[20];
    int age;
    char sex;
} STU;
STU 是 struct stu 的别名,可以用 STU 定义结构体变量:
STU body1,body2;
它等价于:
struct stu body1, body2;
4、给指针类型定义别名:
typedef int (*PTR_TO_ARR)[4];
表示 PTR_TO_ARR 是类型int * [4]的别名,它是一个二维数组指针类型。接着可以使用 PTR_TO_ARR 定义二维数组指针:
PTR_TO_ARR p1, p2;
按照类似的写法,还可以为函数指针类型定义别名:
typedef int (*PTR_TO_FUNC)(int, int);
PTR_TO_FUNC pfunc;
5、typedef与define的区别:
1) 可以使用其他类型说明符对宏类型名进行扩展,但对 typedef 所定义的类型名却不能这样做。如下所示:
#define INTERGE int
unsigned INTERGE n;  //没问题
 
typedef int INTERGE;
unsigned INTERGE n;  //错误,不能在 INTERGE 前面添加 unsigned
 
2) 在连续定义几个变量的时候,typedef 能够保证定义的所有变量均为同一类型,而 #define 则无法保证。例如:
#define PTR_INT int *
PTR_INT p1, p2;
经过宏替换以后,第二行变为:
int *p1, p2;
这使得 p1、p2 成为不同的类型:p1 是指向 int 类型的指针,p2 是 int 类型。
 
typedef int * PTR_INT
PTR_INT p1, p2;
p1、p2 类型相同,它们都是指向 int 类型的指针。
 位运算
按位与&
只有参与&运算的两个位都为 1 时,结果才为 1,否则为 0。
按位与运算通常用来对某些位清 0,或者保留某些位。
按位或|
参与|运算的两个二进制位有一个为 1 时,结果就为 1,两个都为 0 时结果才为 0。
按位或运算可以用来将某些位置 1,或者保留某些位。
按位异或^
参与^运算两个二进制位不同时,结果为 1,相同时结果为 0。
按位异或运算可以用来将某些二进制位反转。
取反~
取反运算符~为单目运算符,右结合性,作用是对参与运算的二进制位取反
左移<<
左移运算符<<用来把操作数的各个二进制位全部左移若干位,高位丢弃,低位补0。
如果数据较小,被丢弃的高位不包含 1,那么左移 n 位相当于乘以 2 的 n 次方。
右移>>
右移运算符>>用来把操作数的各个二进制位全部右移若干位,低位丢弃,高位补 0 或 1。
如果被丢弃的低位不包含 1,那么右移 n 位相当于除以 2 的 n 次方
 内存管理
程序结构:
栈区;堆区;数据区;代码区;地址由高到低
栈区和堆区的内存是运行时由系统进行分配;数据区和代码区是编译器编译时分配;
栈区:
栈又叫堆栈,存的是所有的自动变量(局部变量)、函数形参,这个是由系统进行自动完成,不需要程序员自己考虑存放。栈它的操作方式类型与数据结构中的栈,都是先进后出的操作。当一个函数被调用的时候,这个函数的返回地址和一些其他的调用信息,都会被存放到栈区。然后这被调用的函数再为它的自动变量(局部变量)还有一些形参在栈区分配内存空间,这就是C实现递归调用的方法。
堆区:
这个区是由程序员自己管理的,在程序运行过程中进行动态分配的内存。可以用malloc()系列函数进行动态的添加和释放。堆的大小并不是固定的,可以动态的扩张或者收缩。
数据区:
数据区分为数据段和为初始化的数据段;数据段包含了程序中明确被初始化的全局变量、静态变量(包括全局静态变量和局部静态变量)。未初始化的数据段存放的是全局和静态的(全局静态和局部静态)未初始化变量。
代码区:
所有的可执行代码(包括程序指令、常量字符串等)都加载到代码区,这块内存在程序运行的期间是不变的。
内存分配方式:
静态分配:
编译器在处理程序源代码时分配包含代码区和数据区;
动态分配:
栈区:系统分配;堆区:程序员调用malloc系列函数分配
内存管理函数:
malloc:
p=(char *)malloc(100);
malloc()函数是在内存的动态存储区中分配一个长度为size字节的连续空间。其参数是一个无符号整型数,返回一个指向所分配的连续存储域的起始地址的指针。当函数未能成功分配存储空间时(如内存不足)则返回一个NULL指针。
由于内存区域总是有限的,不能无限制地分配下去,而且程序应尽量节省资源,所以当分配的内存区域不用时,则要释放它,以便其他的变量或程序使用。
realloc:
realloc()函数用来从堆上分配内存,当需要扩大一块内存空间时,realloc()试图直接从堆上当前内存段后面的字节中获得更多的内存空间,如果能够满足,则返回原指针;如果当前内存段后面的空闲字节不够,那么就使用堆上第一个能够满足这一要求的内存块,将目前的数据复制到新的位置,而将原来的数据块释放掉。如果内存不足,重新申请空间失败,则返回NULL。
当调用realloc()函数重新分配内存时,如果申请失败,将返回NULL,此时原来指针仍然有效,因此在程序编写时需要进行判断,如果调用成功,realloc()函数会重新分配一块新内存,并将原来的数据拷贝到新位置,返回新内存的指针,而释放掉原来指针(realloc()函数的参数指针)指向的空间,原来的指针变为不可用(即不需要再释放,也不能再释放)
因此,一般不使用以下语句:
ptr = realloc(ptr, new_amount);
calloc:
calloc是malloc函数的简单包装,它的主要优点是把动态分配的内存进行初始化,全部清零。
ptr = (int *)calloc(count, sizeof(int));
memset:
将s中当前位置后面的n个字节 用 c 替换并返回 s 
void *memset(void *s, int c, size_t n);
free:
free()函数释放原先申请的内存空间。
void free(void *ptr);
堆和栈的区别:
管理方式不同:
栈编译器自动管理,无需程序员手工控制;而堆空间的申请释放工作由程序员控制,容易产生内存泄漏。
空间大小不同:
栈是向低地址扩展的数据结构,是一块连续的内存区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,当申请的空间超过栈的剩余空间时,将提示溢出。因此,用户能从栈获得的空间较小。
        堆是向高地址扩展的数据结构,是不连续的内存区域。因为系统是用链表来存储空闲内存地址的,且链表的遍历方向是由低地址向高地址。由此可见,堆获得的空间较灵活,也较大。栈中元素都是一一对应的,不会存在一个内存块从栈中间弹出的情况。
是否产生碎片:
对于堆来讲,频繁的malloc/free(new/delete)势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低(虽然程序在退出后操作系统会对内存进行回收管理)。对于栈来讲,则不会存在这个问题。
增长方向不同:
堆的增长方向是向上的,即向着内存地址增加的方向;栈的增长方向是向下的,即向着内存地址减小的方向
分配方式不同:
堆都是程序中由malloc()函数动态申请分配并由free()函数释放的;栈的分配和释放是由编译器完成的,栈的动态分配由alloca()函数完成,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行申请和释放的,无需手工实现。
分配效率不同:
栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行。堆则是C函数库提供的,它的机制很复杂,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大的空间,如果没有足够大的空间(可能是由于内存碎片太多),就有需要操作系统来重新整理内存空间,这样就有机会分到足够大小的内存,然后返回。显然,堆的效率比栈要低得多。
常见的内存错误:
指针没有指向一块合法的内存:
没有为结构体成员分配空间,用malloc分配空间:
没有为结构体指针分配足够内存
为指针分配的内存太小,导致越界;
内存分配成功但未初始化
内存越界,常见在数组中
内存泄露,就是说由malloc 系列函数或new 操作符分配的内存。如果用完之后没有及时 free 或 delete,这块内存就无法释放,直到整个程序终止。
内存释放之后,野指针很危险。free 完之后,一定要给指针置 NULL。
内存已经释放了,但是继续通过指针来使用 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值