C语言知识 (大体总结)

一、C语言初级

  1. 初识C语言

  1. 什么是C语言

  1. 一门通用的计算机语言

  1. 有国际标准:C89 ,C90,C99 ,C91等 前两个最多使用

  1. C语言是一门编译型语言,需要编译器

ii.数据类型 :整形char、short、int 、long 、long int、long long、

浮点型: float 、double、 long double

iii.常量和变量

  1. 常量:C语言中不能改变的量

  1. 变量:C语言中可以改变的量

1.1变量分类:

  1. 全局变量:在代码块外部定义的变量

  1. 局部变量:在代码块内部定义的变量

1.2变量的作用域和生命周期

  1. 作用域:限定变量可用的代码范围

局部变量的作用域:变量所在的局部范围

全局变量的作用域:整个工程

  1. 生命周期:变量的创建到变量的销毁之间的一个时间段

局部变量的生命周期:进入作用域开始,出作用域结束

全局变量的生命周期:整个程序

2.常量分量:

字面常量

#define定义的标识符常量

const修饰的常变量

枚举常量

Ⅳ.字符串+转义字符:

  1. 字符串:由“”引起来的一串字符

字符串的结束标志是\0字符

2,转义字符:常用转义字符\'' \' \n \t \r \ddd \xdd

Ⅴ.常见关键字:

auto break case char const continue default do double else enum extern float for int long register return short signed sizeof static struct switch typedef union unsigned void volatile while

1.1关键字是不能自己创建的

1.2自定义的变量名和关键字不能重名

2.1static修饰:

2.1.1修饰局部变量:

2.1.2修饰全局变量:

2.1.3修饰函数:

3.typedef:变量重命名

#define 定义符号:符号没有参数

#define定义宏:宏有参数

  1. 分支语句和循环语句

语句:表达式语句 函数调用语句 控制语句 复合语句 空语句

分支语句:if :悬空else与最近的if连用

、if语句不加括号的情况下只能作用一条语句

switch判断+整型常量表达式:case:语句没有先后顺序 :

break:跳出switch语句

default :位置任意 case语句未执行才执行

循环语句:

while :break 跳出while循环,中止整个循环

continue:跳出本次循环continue后边的代码,直接去循环的判断部分

for:初始化部分 :条件判断部分 :调整部分

break:跳出for循环,中止整个循环

continue:跳出本次循环,直接去循环的调整部分

do while :先执行后判断的,循环体至少执行一次

break 跳出循环

continue:跳出本次循环中continue后面的 代码

折半查找算法:二分查找

在已经有序的数列中查找元素

goto语句:不建议大量使用 :适用于多层循环嵌套的情况

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
int main()
{
    int k = 0;
    scanf("%d", &k);//查找的数字
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };//在此数组内容中查找
    int sz = sizeof(arr) / sizeof(arr[0]);//数组中整数个数
    int left = 0;
    int right = sz - 1;
    int mid = 0;
    while (left <= right)
    {
        mid = (left + right) / 2;
        if (arr[mid] > k)
        {
            right = mid -1;
        }
        else if (arr[mid] < k)
        {
            left = mid +1;
        }
        else
        {
            break;
        }
    }
    if (left <= right)
        printf("找到了,下标是%d", mid);
    else
        printf("没找到");
    return 0;
}
  1. 函数:完成某项特定任务,相较于其他代码,具备相对的独立性

  1. 函数的分类:库函数

  1. 库函数的简单分类

  1. IO函数

  1. 字符串函数

  1. 字符函数

  1. 内存操作函数

  1. 时间日期函数

  1. 数学函数

  1. 其他函数

ii.自定义函数:

函数的参数:实参:真实传递给函数的参数:常量,变量。表达式 函数等

形参:函数名括号中的变量:只有在函数调用的时候才会实例化

函数的调用:传值调用:

函数的形参和实参分别占有不同的内存块,对形参的修改不会影响实参

传值调用:

把函数外变量的内存地址传递个函数参数

函数和函数外变量,有了真正的联系,函数内部可以直接操作函数外变量

函数的嵌套调用:函数可以嵌套调用但不能嵌套定义

函数的链式访问:

函数的声明和定义:

函数声明:一般放在头文件中

函数声明要在使用之前,必须满足先声明后使用

函数定义:函数的具体实现 :函数的返回类型 函数名 (参数)

函数递归:大事化小

递归的两个条件

存在某个限制条件:当条件满足时,递归不再继续

每次递归调用都想这个限制条件接近

简单练习:模拟实现strlen 递归方式

int my_strlen(char* arr)//函数的返回值的使用实现链式访问
{
    if (*arr == '\0')
        return 0;
    else
        return 1 + my_strlen(arr + 1);
}
int main()
{
    char arr[] = "abadsdfsdfsdf";
    printf("%d", my_strlen(arr));//链式访问意义
    return 0;
}

3.数组

  1. 一维数组

  1. 一维数组的创建和初始化

  1. 数组的创建:数组是一组相同数据类型元素的集合

  1. 数组创建时[]中要给一个常量(C99)之前

  1. 数组的初始化:创建数组时可不指定数组的大小,元素个数根据初始化的内容确定

ii.一维数组的使用:

数组是用下标来访问的,下标是从0开始的

数组的大小似乎可以通过计算得到的 :sizeof(arr)/sizeof(arr[0])

iii.一维数组在内存中的存储:

数组在内存中是连续存放的 元素地址随数组下标的增长而递增

b.二维数组

二维数组的创建和初始化

初始化:行可以省略,列不能省略

二维数组的使用:也是通过下标来访问的

二维数组的存储:在内存中也是连续存储的

c.数组越界:超出合法空间的访问

如果数组有n个元素,下标如果小于0,或者大于n-1,就是数组越界

二维数组的行和列也存在越界可能

C语言本身是不做数组下标的月结检查,编译器也不一定报错

d.数组作为函数参数:数组传参是只是传输了数组的首元素的地址

数组传参的时候需要将数组长度设为函数参数

两种情况之外数组名都表示数组首元素的地址

sizeof(数组名)计算整个数组的大小,单独放数组名,数组名表示整个数组

&数组名,去除的是数组的地址,&数组名 ,数组名表示整个数组

c语言操作符

操作符分类:

1.算数操作符:+ - * / %

%操作符的两个操作数必须为整数

/ 操作符的两个操作数都为证书,执行整数除法,只要有浮点数执行浮点数除法(这里面有 隐式类型转换

2.移位操作符 << >> 操作数只能是整数 是运算 不影响操作数变量内的数据

左移操作符:左边丢弃,右边补零

右移操作符:逻辑右移:右边丢弃,左边补零 :针对无符号数进行的操作

算数右移:右边丢弃,左边用原值符号位填充

  1. 位操作符 :操作数必须是整数

  1. &按位与 二进制中对应位数全相同才为1,其余为0

  1. |按位或 二进制中对应位数只要有一个为1,就是1

3.^异或:相同为0 ,不同为1

  1. 赋值操作符:不推荐连续赋值操作

  1. 复合赋值符+= -= *= /= %= >>= <<= &= |= ^=

c.单目操作符:

! 逻辑反操作 - 负值 + 正值 & 取地址 sizeof 操作数的类型长度(以字节为单位) ~ 对一个数的二进制按位取反 -- 前置、后置-- ++ 前置、后置++ * 间接访问操作符(解引用操作符) (类型) 强制类型转换

d.关系操作符:

> >= < <= != 用于测试“不相等” == 用于测试“相等”

e.逻辑操作符:

&& 逻辑与 :两个条件都为真才为真 前一个为0后面的就不进行 || 逻辑或:只要有一个为真就为真 前一个为1后面就不进行了

f.条件操作符:exp1?exp2:exp3; 表达式1为真,执行2,否则执行3

g.逗号表达式:用逗号隔开的多个表达式 从左向右依次执行,结果是最后一个表达式的结果

下标引用、函数调用和结构成员

结构体.成员名

结构体指针->成员名

表达式求值:表达式求值的顺序一部分是有操作符的优先级和结核性九鼎

有些表达式的操作数在求值的过程中,可能要转换成其他类型

隐式类型转换:

整型提升:有符号整数的整型提升: 高位补符号位

无符号整数的整形提升:高位补零

整形截断:

算数转换:操作符的各个操作数属于不同的类型,那麽除非其中一个操作数的转换为另一 个操作数的类型,否则操作就无法进行

long double double float unsigned long int long int unsigned int int

操作符的属性:

操作符的优先级

操作符的结合性

是否控制求值顺序

相邻两个操作符先执行哪个:取决于它们的优先级。如果二者的优先级相同,取决于他们的结合性

问题表达式:表达式不能通过操作符的属性确定唯一的计算途径,表达式就是存在问题的

  1. 指针初阶:
  1. 指针是什么:内存中一个最小单元的编号

通常指的是用来存放内存地址的指针变量

地址是唯一标识一块地址空间的

指针的大小在32位平台是4个字节,在64位平台是8个字节

  1. 指针类型

  1. 指针+-整数:指针的类型决定了指针向前或者向后走一步有多大

  1. 指针的解引用:指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)

目标数据将作为什么类型进行解读

c.野指针:就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

成因:指针未初始化 指针越界访问 指针指向的空间释放

如何规避:指针初始化 小心指针越界 指针指向的空间释放及时置NULL

避免返回局部变量的地址 指针使用之前检查有效性

d.指针运算: 指针+-整数

指针- 指针

指针的关系运算:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指 针比较,但是不允许与只想第一个元素之前的内存位置的指针进行比较

e.指针和数组:数组名表示数组首元素的地址

可以直接通过指针来访问数组

二级指针:指针变量也是变量 , 保存指针变量地址的就是二级指针

指针数组 存放指针的数组

  1. 初识结构体:

  1. 结构体是一些值的集合,这些值称为成员变量

  1. 结构的每个成员可以是不同类型的变量

  1. 结构体的声明

  1. 结构体的定义和初始化

struct S

{

char name[];

int age;

char address[];

}s;//此处的s是全局变量

v.结构体成员的访问:结构体.成员名

结构体指针->成员名

vi.结构体传参: 传结构体的地址

如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,大致性能的下降

  1. 实用调试技巧:
  1. 调试:是发现和减少计算机程序或电子仪器设备中程序错误的一个过程

  1. 基本步骤:发现程序错误的存在

  1. 以隔离消除等方式会错误进行定位

  1. 确定错误产生的原因

  1. 提出纠正错误的解决办法

  1. 对程序错误予以改正,重新测试

Debug和Release的介绍 Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。 Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是 最优的,以便用户很好地使用。

windows环境调试介绍 F5 :启动调试,经常用来直接调到下一个断点处。 F9 :创建断点和取消断点 断点的重要作用,可以在程序的任意位置设置断点。这样就可以使得程 序 在 想要的位置随意停止执行,继而一步步执行下去。 F10 逐过程,通常用来处理一个过程,一个过程可以是一次函数调用,或者是一条语句。 F11 逐语句,就是每次都执行一条语句,但是这个快捷键可以使我们的执行逻辑进入函数内部(这 是 最长用的)。 CTRL + F5 开始执行不调试,如果你想让程序直接运行起来而不调试就可以直接使用。 3. 调试的时候查看程序当前信息 查看临时变量的值

查看调用堆栈

查看汇编信息

查看寄存器信息

11.如何写出好(易于调试)的代码。 优秀的代码: 1. 代码运行正常 2. bug很少 3. 效率高 4. 可读性高 5. 可维护性高 6. 注释清晰 7. 文档齐全 常见的coding技巧: 1. 使用assert 2. 尽量使用const 3. 养成良好的编码风格 4. 添加必要的注释 5. 避免编码的陷阱

结论: const修饰指针变量的时候: 1. const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改 变。但是指针变量本身的内容可变。 2. const如果放在*的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指 针指向的内容,可以通过指针改变。

编程常见的错误: 编译型错误 链接型错误:一般是标识符名不存在或拼写错误

运行时错误

二.C语言进阶

1.数据的存储:

数据的类型;

基本归类:整型家族: char short int long

浮点型家族: float double

构造类型: 数组类型 结构体类型 枚举类型:enum 联合类型 union 指针类型

空类型:void 表示空类型(无类型)通常应用于函数的返回类型,函数的参数,指针类型

整形在内存中的存储:

原码补码反码:计算机中的整数的三种表示方法

整数有符号位和数值位两部分 :符号位0表示正1表示负

正整数的原码反码补码都相同

负整数的三种表示方法各不相同

原码:按照正负数的形式翻译成二进制

反码:符号位不变其余按位取反

补码:反码加一

对于整形来说:数据存放在内存中的时补码

原因:是用补码,可以将符号和数值域统一处理

cpu只有加法器,不骂与源码相互转换,其运算过程相同,不需要额外的硬件电路

大小端的介绍:

大端存储模式:是指数据的高位保存在内存的低地址中,数据的高位,保存在内存的低地址处

小端存储模式:是指数据的低位保存在低地址中,数据的高位,保存在内存的高地址处,

2.指针的进阶:

字符指针:存放字符地址的: 指向字符串时,是首字符地址

字符数组首元素地址是字符指针

指针数组:存放指针的数组

数组指针:指向数组的指针

指向数组指针:int (*p)[10];

[]优先级高于*,所以必须加上()来保证p和*结合

&数组名VS数组名:

&arr和arr值是一样的

&arr的类型是 int (*p)[10];是一种数组指针类型

数组参数、指针参数:

一维数组传参

二维数组传参:函数形参的设计只能省略第一个[]的数字

一级指针传参:函数的参数为一级指针的时候,可以接收的参数:

相应类型变量的地址

相应类型数组的数组名

二级指针传参:函数的参数为二级指针的时候,可以接收的参数:

一级指针的地址

二级指针

指针数组的数组名

函数指针:指向函数的指针:void (*pc)():指向的时一个函数 函数参数是无参数,返回值类型为void 函数指针数组:int (*ps[23])() 函数指针数组的用途 转移表 指向函数的指针的数组

指向函数指针数组的指针:int(*(*pc)[10])();

回调函数:回调函数不是由该函数的实现方直接调用,二十在特定的时间或条件发生时有另外的一方调 用的用于对该事件或条件进行响应

  1. 字符串和内存函数的介绍

  1. strlen:函数的返回值时size-t,是无符号的 size-t

另外两种模拟实现方式:

1.指针减指针
size_t my_strlen(const char* arr)//函数的返回值的使用实现链式访问
{
    if (*arr == '\0')
        return 0;
    else
        return 1 + my_strlen(arr + 1);
}
size-t my_strlen(const char* arr,int sz)
{
    return (arr+sz-1)-arr;
}
//加法
size_t my_strlen(const char* arr)
{
    int count = 0;
    while (*arr != '\0')
    {
        arr++;
        count++;
    }
    return count;
}
int main()
{
    char arr[] = "abadsdfsdfsdf";
    int sz = sizeof(arr) / sizeof(arr[0]);
//    printf("%d", my_strlen(arr));//链式访问意义
    printf("%zu", my_strlen(arr,sz));//链式访问意义
    return 0;
}

ii.长度不受限制的字符串函数

strcpy:char*strcpy(char*destnation,const char*source)

目标空间必须可变且空间必须足够的大

char* my_strcpy(char* dest, const char* sur)
{
    assert(dest && sur);
    char* ret = dest;
    while (*dest++ = *sur++)
    {
        ;
    }
    return ret;
}
int main()
{
    char arr1[10] = "asdfjkoad";
    char arr2[20] = { 0 };
    printf("%s", my_strcpy(arr2, arr1));
    return 0;
}

iii.strcat: char* strcat(char*destnation,const char*source)

目标空间必须可变且空间必须足够的大

char* my_strcat(char* dest, const char* sur)
{
    assert(dest && sur);
    char* ret = dest;
    while (*dest != '\0')
    {
        dest++;
    }
    while (*dest++ = *sur++)
    {
        ;
    }
    return ret;
}
int main()
{
    char arr1[20] = "skyisshy";
    char arr2[39] = "shy is sky";
    printf("%s", my_strcat(arr1, arr2));
    return 0;
}

iv.strcmp: int strcmp(const char*str1,const char* str2);

第一个字符串大于第二个字符串,则返回大于0的数字

第一个字符串等于第二个字符串,则返回0;

第一个字符串小于第二个字符串,则返回小于0的数字。

int my_strcmp(const char* str1, const char* str2)
{
    assert(str1 && str2);
    while (*str1 == *str2)
    {
        if (*str1 == '\0')
            return 0;
        str1++;
        str2++;
    }
    return *str1 - *str2;
}
int main()
{
    char arr1[239] = "abcdeq";
    char arr2[23] = "abcdef";
    printf("%d", my_strcmp(arr1, arr2));
    return 0;
}

长度受限制的字符串函数

strncpy char*strncpy(char*destnation,const char*source,size_t num);

char* my_strncpy(char* dest, const char* sur,int num)
{
    assert(dest && sur);
    char* ret = dest;
    while (num&&*sur !='\0')
    {
        *dest++ = *sur++;
         num--;
    }
    while(num--)
     {
           *dest++ = '\0';
     }
    return ret;
}
int main()
{
    char arr1[10] = "abcdefghi";
    char arr2[123] = { 0 };
    int num = 6;
    printf("%s", my_strncpy(arr2, arr1, num));
    return 0;
}

vi strncat char* strncat(char*destination,const char* source,size_t num);

char* my_strncat(char* dest, const char* sur, int num)
{
    char* ret = dest;
    while (*dest)
    {
        dest++;
    }
    while (*sur != '\0' && num)
    {
        *dest++ = *sur++;
        num--;
    }
    return ret;
}
int main()
{
    char arr1[10] = "abc";
    char arr2[123] = "xxxxxxx";
    int num = 2;
    printf("%s", my_strncat(arr2, arr1, num));
    return 0;
}

strncmp int strncmp(const char*str1,const char*str2);

int my_strncmp(const char* str1, const char* str2,int num)
{
    while ( * str1 == *str2 && num)
    {
        if (*str1 == '\0')
            return 0;
        num--;
        str1++;
        str2++;
    }
    return *str1 - *str2;
}
int main()
{
    char arr1[19] = "abkdsk";
    char arr2[29] = "abzdeq";
    int num = 6;
    printf("%d", my_strncmp(arr1, arr2, num));
    return 0;
}

strstr char* strstr(const char*str1,const char*str2)

char* my_strstr(const char* arr1, const char* arr2)
{
    assert(arr1 && arr2);
    const char* ret = arr1;
    const char* sn = arr2;
    while (*ret != *sn && *ret != '\0')
    {
        ret++;
    }
    while (*sn)
    {
        const char* sdf = ret;
        while (*ret++ == *sn++)
        {
            if (*sn == '\0')
                return (char*)sdf;
        }
        ret++;
    }
    return NULL;
}
int main()
{
    char arr1[29] = "abcdef";
    char arr2[23] = "cde";
    printf("%s", my_strstr(arr1, arr2));
    return 0;
}

memmove void* memmove(void* destination,const void* source,size_t count)

//void* my_memmove(void* dest, const void* src, size_t count)
//{
//    assert(dest && src);
//    void* ret = dest;
//    if (dest < src)
//    {
//        while (count--)
//        {
//            *(char*)dest = *(char*)src;
//            dest = (char*)dest + 1;
//            src = (char*)src + 1;
//        }
//    }
//    else
//    {
//        while (count--)
//        {
//            *((char*)dest + count) = *((char*)src + count);
//        }
//    }
//    return ret;
//}
//int main()
//{
//    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//    my_memmove(arr, arr + 2, 20);
//    int i = 0;
//    int sz = sizeof(arr) / sizeof(arr[0]);
//    for (i = 0; i < sz; i++)
//    {
//        printf("%d ", arr[i]);
//    }
//    return 0;
//}

memcpy void*memcpy (void* destination,const void* source,size_t count)

void* my_memcpy(void* dest, const void* src, size_t count)
{
    assert(dest && src);
    void* ret = dest;
    while (count--)
    {
        *(char*)dest = *(char*)src;
        src = (char*)src + 1;
        dest = (char*)dest + 1;
    }
    return ret;
}
//强制类型转换不是永久性地转换
int main()
{
    int arr[29] = { 1,2,3,4,5,6,7,8,9,0 };
    int arr1[210] = { 0 };
    my_memcpy(arr1, arr, 20);
    int i = 0;
    for (i = 0; i < 20 / 4; i++)
    {
        printf("%d ", arr1[i]);
    }
    return 0;
}

strtok char*strtok(char*str,const char*sep);

strtok函数找到str中的下一个标记,并将其用\0结尾,返回一个指向这个标记的指针。

注意:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串,一般都是临时拷贝 的内容且可以修改

函数的第一个参数不为NULL,函数将找到str中第一个标记,strtok函数将保存他在字符串中的 位置

函数的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记

字符串中不存在更多的标记,则返回NULL指针

错误信息报告

strerror char*sterror(int errnum);

返回错误码对应的错误信息

perror

字符函数内存操作函数

memcpy void* memcpy(void* destination,const void *source,size_t num);//num是字节数

函数在遇到‘\0'的时候并不会停下来

source和destination有任何的重叠,复制的结果都是未定义的

memmove :源内存块和目标内存块时可以重叠的

memcmp:所有类型都可以进行比较

memset:所有类型赋值操作

//void* my_memmove(void* dest, const void* src, size_t count)
//{
//    assert(dest && src);
//    void* ret = dest;
//    if (dest < src)
//    {
//        while (count--)
//        {
//            *(char*)dest = *(char*)src;
//            dest = (char*)dest + 1;
//            src = (char*)src + 1;
//        }
//    }
//    else
//    {
//        while (count--)
//        {
//            *((char*)dest + count) = *((char*)src + count);
//        }
//    }
//    return ret;
//}
//int main()
//{
//    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//    my_memmove(arr, arr + 2, 20);
//    int i = 0;
//    int sz = sizeof(arr) / sizeof(arr[0]);
//    for (i = 0; i < sz; i++)
//    {
//        printf("%d ", arr[i]);
//    }
//    return 0;
//}
  1. 自定义类型详解:

  1. 结构体:

  1. 匿名结构体类型:

  1. 声明结构的时候,可以不完全声明,省略掉了结构体标签

  1. 两个成员完全相同的匿名结构类型,不是同一个类型

ii.结构体的自引用

iii.结构体变量的定义和初始化

iv.结构体内存对齐://用空间换取效率

规则:1. 第一个成员在与结构体变量偏移量为0的地址处。 2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值 3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。 4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结 构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

为什么存在内存对齐:1. 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据 的某些硬件平台只能 在某些地址处取某些特定类型的数据,否则抛出硬件异常。 2. 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在 于,为了访问未对齐的 内存,处理器需要作两次内存访问;而对齐的内存访问仅 需要一次访问。

节省空间的技巧:让占用空间小的成员尽量集中在一起

修改默认对齐数:

#pragma pack(1)//设置默认对齐数为1

#pragma pack()//取消设置的默认对齐数,还原为默认

结构体传参:穿结构体的地址

原因:函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。如果传递一个结 构 体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。

vi.位段:1.位段的成员必须是 int、unsigned int 或signed int 。 2.位段的成员名后边有一个冒号和一个数字。

位段的内存分配 1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型 2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。 3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

位段的跨平台问题 1. int 位段被当成有符号数还是无符号数是不确定的。 2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机 器会出问题。 3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。 4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是 舍弃剩余的位还是利用,这是不确定的。 总结: 跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。

枚举的优点:1. 增加代码的可读性和可维护性 2. 和#define定义的标识符比较枚举有类型检查,更加严谨。 3. 防止了命名污染(封装) 4. 便于调试 5. 使用方便,一次可以定义多个常量

联合:union :特征是这些成员公用同一块 空间(所以联合也叫共用体)。 一个联合变量的大小, 至少是最大成员的大小(因为 联合至少得有能力保存最大的那个成员)。

联合大小的计算 联合的大小至少是最大成员的大小。 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

  1. 动态内存管理:

  1. 动态内存函数:生命放在stdlib.h头文件中

  1. malloc :void* malloc(size_t size);向内存申请一块连续可用的空间,不返回指向这块空间的指针

  1. 开辟成功返回指针,开辟失败返回NULL所以malloc返回值一定要做检查 参数大小为0,是标准未定义的取决于编译器

  1. free :void free(void*ptr); 用来释放动态开辟的内存的

  1. ptr指向的空间不是动态开辟的,那free函数的行为是未定义的

  1. ptr是NULL指针,则函数什么也不做

  1. calloc: void* calloc(size_t num,size_t size);为num个大小为size的元素开辟一块空间

把所开辟的空间的每个字节初始化为0

  1. realloc:函数就可以做到对动态开辟内存大小的调整 void* realloc(void*ptr,size_t size);

  1. ptr 是要调整的内存地址 size 调整之后新大小 返回值为调整之后的内存起始位置。 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 新 的空间。 realloc在调整内存空间的是存在两种情况: 情况1:原有空间之后有足够大的空间

  1. 直接在原有内存之后直接追加空间,原来空间的数据不发生变化

情况2:原有空间之后没有足够大的空间

ii.在推空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新 的内存地址

常见的动态内存错误:

对NULL指针的解引用操作

对动态开辟空间的越界访问

对非动态动态内存使用free释放

使用free释放一块动态开辟内存的一部分

对一块动态内存多次释放

动态开辟内存忘记释放(内存泄漏)

C/C++程序内存分配的几个区域: 1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些 存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有 限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。 2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似 于链表。 3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。 4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。

柔性数组:

特点:结构中的柔性数组成员前面那必须至少有一个其他成员

sizeof返回这种结构大小不包括柔性数组的内存

包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应 柔性数组的预期大小

优势:方便内存释放

有利于访问速度

6. 文件操作:FILE* pf;//文件指针变量

文件打开关闭:

FILE * fopen ( const char * filename, const char * mode ); int fclose ( FILE * stream );

文件使用方式 含义 如果指定文件不存在 “r”(只读) 为了输入数据,打开一个已经存在的文本文件 出错 “w”(只写) 为了输出数据,打开一个文本文件 建立一个新的文件 “a”(追加) 向文本文件尾添加数据 出错 “rb”(只读) 为了输入数据,打开一个二进制文件 出错 “wb”(只写) 为了输出数据,打开一个二进制文件 建立一个新的文件 “ab”(追加) 向一个二进制文件尾添加数据 出错 “r+”(读写) 为了读和写,打开一个文本文件 出错 “w+”(读写) 为了读和写,建议一个新的文件 建立一个新的文件 “a+”(读写) 打开一个文件,在文件尾进行读写 建立一个新的文件 “rb+”(读写) 为了读和写打开一个二进制文件 出错 “wb+”(读写) 为了读和写,新建一个新的二进制文件 建立一个新的文件 “ab+”(读写) 打开一个二进制文件,在文件尾进行读和写 建立一个新的文件

ftell long int ftell ( FILE * stream ); 返回文件指针相对于起始位置的偏移量

rewind void rewind ( FILE * stream ); 让文件指针的位置回到文件的起始位置

文件结束判定 被错误使用的 feof 牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。 而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。 1. 文本文件读取是否结束,判断返回值是否为EOF (fgetc),或者NULL(fgets) 例如: fgetc判断是否为EOF. fgets判断返回值是否为NULL. 2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。 例如: fread判断返回值是否小于实际要读的个数

fseek int fseek ( FILE * stream, long int offset, int origin ); 根据文件指针的位置和偏移量来定位文件指针。

文件的顺序读写:

功能 函数名 适用于 字符输入函数 fgetc 所有输入流 字符输出函数 fputc 所有输出流 文本行输入函数 fgets 所有输入流 文本行输出函数 fputs 所有输出流 格式化输入函数 fscanf 所有输入流 格式化输出函数 fprintf 所有输出流 二进制输入 fread 文件 二进制输出 fwrite 文件

7.预编译:

符号:语言内置的

__FILE__ //进行编译的源文件 __LINE__ //文件当前的行号 __DATE__ //文件被编译的日期 __TIME__ //文件被编译的时间 __STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义

#define

  1. 定义标识符:#define name stuff 最后建议不要加上 ; ,这样容易导致问题

  1. #define 定义宏 :#define 机制包括了一个规定,允许把参数替换到文本中

  1. 注意:用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。

  1. #define 替换规则 在程序中扩展#define定义符号和宏时,需要涉及几个步骤。 1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。 2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。 3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

  1. 注意: 1. 宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。 2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

  1. 宏和函数对比 1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序 的规模和速度方面更胜一筹。 2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可 以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。 当然和宏相比函数也有劣势的地方: 1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。 2. 宏是没法调试的。 3. 宏由于类型无关,也就不够严谨。 4. 宏可能会带来运算符优先级的问题,导致程容易出现错。 宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值