BIT - 5 数据在内存中的存储及字符函数( 10000字详解 )

一:整型在内存中的存储

在 C 语言中,整型数据使用固定长度的字节来存储。通常情况下,int 类型占用 4 个字节(32位),其存储方式有 3 种,包括原码、反码和补码。

  1. 原码:

原码是最直观的表示方式,它将整数直接转换为二进制,并在最高位(最左边的位)上标识正负号。对于正数,最高位为0;对于负数,最高位为1。

例如:

  • 整数75的原码表示为:00000000 00000000 00000000 01001011,
  • 而整数-75的原码表示为:10000000 00000000 00000000 01001011

他们的区别就在最高的符号位上

  1. 反码:

反码的表示方式中,正数的反码与原码相同,而负数的反码是在原码的基础上,除了最高位之外,其它位按位取反。

例如:

  • 整数75的反码表示为:00000000 00000000 00000000 01001011
  • 对于75的反码表示为:11111111 11111111 11111111 10110100。

对于 75 来说,原码补码相同,而对于 -75 来说则需要进行转换了

  1. 补码:

正数的补码与原码相同,而负数的补码是在反码的基础上加1。

例如:

  • 整数75的补码表示为:00000000 00000000 00000000 01001011
  • 对于-75的补码表示为:11111111 11111111 11111111 10110101。

总结:

  • 对于正数来说,正数的原码,反码,补码都相同

  • 对于负数来说,负数的原码,反码,补码不相同

负数的原码,反码,补码转化如图所示:

在这里插入图片描述

举个例子:求一下 int a = 198 和 int b = -289 在内存的原码反码和补码

对于 int a = 198 来说,它的原码反码和补码都相同,都是

  • 00000000 00000000 00000000 11000110

对于int b = -289来说,

  • 原码: 10000000 00000000 00000001 00100001
  • 反码: 11111111 11111111 11111110 11011110
  • 补码: 11111111 11111111 11111110 11011111

注意:数据通过读直接写出来的是原码,但是数据在内存中存储的是补码

二:大小端介绍

  • 大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;
  • 小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地址中。

那什么是数据的低位呢?
在这里插入图片描述

三:浮点型在内存中的存储

在 c 语言中,我们对浮点型在内存中的存储采用 IEEE754标准,任意一个二进制浮点数可以表示成下面的形式:

  • (-1)^S * M * 2^E

对于这个形式我们进行解释:

  1. (-1)^S表示符号位,当 S = 0,V 为正数;当 S = 1,V为负数。

  2. M表示有效数字, 1≤ M <2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中xxxxxx表示小数部分。

  3. ^E表示指数位。

IEEE 754规定:

对于 32 位的浮点数,最高的 1 位是符号位 s,接着的 8 位是指数 E,剩下的 23 位为有效数字 M。
在这里插入图片描述

对于 64 位的浮点数,最高的 1 位是符号位 S,接着的 11 位是指数E,剩下的 52 位为有效数字 M。

在这里插入图片描述

IEEE 754对有效数字M和指数E,还有一些特别规定。

3.1 数字 M:

前面说过, 1 ≤ M < 2 ,也就是说,M 可以写成 1.xxxxxx 的形式,其中 xxxxxx 表示小数部分。

IEEE 754 规定,在计算机内部保存 M 时,因为这个数的第一位总是 1,因此可以被舍去,只保存后面的 xxxxxx 部分。( 可以认为 M 就是 xxxxxx 部分 ) 比如保存 1.01 的时候,只保存 01,等到读取的时候,再把第一位的 1 加上去。

3.2 指数 E

至于指数 E,情况就比较复杂:首先,E 为一个无符号整数,这意味着,如果 E 为 8 位,它的取值范围为 0 ~~ 255 ;如果 E 为 11 位,它的取值范围为 0 ~ 2047。

但是,我们知道,科学计数法中的 E 是可以出现负数的,所以 IEEE 754 规定,存入内存时 E 的真实值必须再加上一个中间数, 对于 8 位的 E,这个中间数 是 127;对于 11 位的 E,这个中间数是 1023。

比如,2^10 的 E 是 10,所以保存成 32 位浮点数时,必须保存成 10+127=137,即 10001001。

3.2.1 进一步细分

然后,指数E从内存中取出还可以再分成三种情况:

  1. E 不全为 0 或不全为 1

这时,浮点数就采用这种规则表示:即指数 E 的计算值减去127(或1023),得到真实值,再将有效数字 M 前加上第一位的 1。

比如:0.5 的二进制形式为 0.1,由于规定正数部分必须为1,即将小数点右移 1 位,则为 1.0*2^(-1),其阶码为 -1+127=126,表示为 01111110,

而尾数 1.0 去掉整数部分变为 0 ,补齐 0 到 23 位00000000000000000000000,则 0.5 的二进制表示形式为: 0 01111110 00000000000000000000000

  1. E 全为 0

这时,浮点数的指数 E 等于 1-127(或者1-1023)即为真实值,有效数字 M 不再加上第一位的 1,而是还原为 0.xxxxxx 的小数。这样做是为了表示 ±0,以及接近于 0 的很小的数字。

  1. E全为1

这时,如果有效数字 M 全为 0,表示±无穷大(正负取决于符号位s);

下面通过一道例题巩固知识:将 20.59375 转换为IEEE754二进制表达方式,首先 20 转换为 2 进制应该是 10100,接着我们对小数部分进行转换:
在这里插入图片描述
所以小数部分就应该是 10011,所以 20.59375 可以转换为 10100.10011 -> (-1)^0 x 1.010010011 x 2^4,即 s = 0 ,e =4+127 = 131 ,m = 010010011(省略了1),所以完整的二进制应该是:

  • 0 10000011 01001001100000000000000

转成 16 进制表示即:41A4C000

四:const 和 assert

4.1 assert

在 C 语言中,assert是一个预处理宏,它用于检查程序中的某个条件是否为真,如果为假,则会终止程序的执行并输出相应的错误消息。

下面是assert的基本语法:

#include <assert.h>

void assert(int expression);

assert宏接受一个表达式作为参数,如果该表达式为假,程序会被终止,并输出包含错误信息的消息。如果表达式为真,则不会有任何影响,程序继续执行。

示例:

#include <assert.h>

int divide(int a, int b) {
    assert(b != 0); // 确保分母不为零

    return a / b;
}

上述代码中,如果b为0,则断言失败,程序会终止执行,并输出错误消息。

4.2 const

在 C 语言中,const 关键字用于声明一个常量,也就是一个不可修改的变量。它可以应用于不同的上下文中,如变量声明、函数参数、函数返回类型等。

代码示例:

int main() {
    const int x = 5; // x 声明为常量,并赋值为 5
    x = 10; // 错误,无法修改常量的值
    const int* p = &x; // p 是指向常量的指针,无法通过 p 修改 x 的值
    int* const q = &x; // q 是一个常量指针,无法修改q的值,即无法改变指针指向
    return 0;
}

我们定义了一个指向常量的指针 p 和一个常量指针 q。无法通过指针 p 修改 x 的值,而 q 无法改变指向

那么,const int* pint *const p之间有什么区别呢?

  • const int* p:表示p是一个指向常量的指针。指针本身可被修改,但不能通过指针修改所指向地址的值。
  • int *const p:表示p是一个常量指针。指针本身是一个常量,不可被修改,但可以通过指针修改所指向地址的值。

简而言之,const int* p允许修改指针本身,但不能通过指针修改值;int *const p允许修改所指向地址的值,但不能修改指针本身。

五:字符函数和字符串函数

5.1 strlen strcpy strcat strcmp

让我们逐个介绍这些 C 语言中的字符串处理函数。

  1. strlen 函数:

strlen 函数用于计算字符串的长度,即字符串中的字符数(不包括空字符 ‘\0’)。

  #include <stdio.h>
     #include <string.h>
     
     int main() {
         char str[] = "Hello, World!";
         int length = strlen(str);
         printf("字符串的长度为:%d\n", length);
         return 0;
     }

运行结果:

字符串的长度为 13。

注意:

  • strlen(地址)括号中放的是地址
  • 参数指向的字符串必须要以 ‘\0’ 结束(虽然不计算\0,但是需要 \0 这个标志)
  • 函数的返回值为size_t,是无符号的( 易错 )
  1. strcpy 函数:

strcpy 函数用于将一个字符串复制到另一个字符串中,包括字符串末尾的空字符 ‘\0’。

  #include <stdio.h>
  #include <string.h>
  
  int main() {
      char source[] = "Hello, World!";
      char destination[20];
      strcpy(destination, source);
      printf("复制后的字符串为:%s\n", destination);
      return 0;
  }

运行结果:

复制后的字符串为 "Hello, World!"。

注意:

  • 源字符串必须以 ‘\0’ 结束。
  • 会将源字符串中的 ‘\0’ 拷贝到目标空间。
  • 目标空间必须足够大且可变,以确保能存放源字符串。
  1. strcat 函数:

strcat 函数用于将一个字符串连接到另一个字符串的末尾,结果是一个新的字符串。

  #include <stdio.h>
  #include <string.h>
  
  int main() {
      char str1[] = "Hello, ";
      char str2[] = "World!";
      strcat(str1, str2);
      printf("连接后的字符串为:%s\n", str1);
      return 0;
  }

运行结果:

连接后的字符串为 "Hello, World!"。

注意:

  • 源字符串必须以 ‘\0’ 结束。
  • 目标空间必须有足够的大且可修改,能容纳下源字符串的内容。
  • 不能自己给自己追加,即strcat(str, str);
  1. strcmp 函数:

strcmp 函数用于比较两个字符串是否相等,或者判断哪个字符串的大小顺序在前。

  #include <stdio.h>
  #include <string.h>
  
  int main() {
      char str1[] = "Hello";
      char str2[] = "Hello";
      int result = strcmp(str1, str2);
      if (result == 0) {
          printf("字符串相等\n");
      } else if (result < 0) {
          printf("字符串1在前\n");
      } else {
          printf("字符串2在前\n");
      }
      return 0;
  }

运行结果:

字符串相等。

标准规定:

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

5.2 strncpy strncat strncmp

  1. strncpy 函数:

strncpy 函数用于将一个字符串的指定长度复制到另一个字符串中。它的函数原型如下:

char* strncpy(char* dest, const char* src, size_t n);
  • dest:目标字符串,用于接收复制后的结果。
  • src:源字符串,要复制的字符串。
  • n:要复制的最大字符数。

strncpy 函数将 src 中的前 n 个字符复制到 dest 中,如果 src 的长度小于 n,则会在 dest 的剩余位置上用空字符填充。

下面是一个使用 strncpy 函数的示例:

#include <stdio.h>
#include <string.h>

int main() {
    char source[] = "Hello, World!";
    char destination[20];

    strncpy(destination, source, 5);
    destination[5] = '\0';  // 手动添加字符串结束符

    printf("Copied string: %s\n", destination);

    return 0;
}

上述代码将 source 字符串的前 5 个字符复制到 destination 字符串中,并手动在第 6 个位置添加了字符串结束符。

  1. strncat 函数:

strncat 函数用于将一个字符串的指定长度追加到另一个字符串的末尾。它的函数原型如下:

char* strncat(char* dest, const char* src, size_t n);
  • dest:目标字符串,要追加的字符串将会添加到这里。
  • src:源字符串,要追加的字符串。
  • n:要追加的最大字符数。

strncat 函数将 src 中的前 n 个字符追加到 dest 的末尾,并确保 dest 仍然以空字符结尾。

下面是一个使用 strncat 函数的示例:

#include <stdio.h>
#include <string.h>

int main() {
    char destination[20] = "Hello, ";
    strncat(destination, "World!", 6);
    printf("Concatenated string: %s\n", destination);
    return 0;
}

上述代码将 "World!" 字符串的前 6 个字符追加到 destination 字符串的末尾。

  1. strncmp 函数:

strncmp 函数用于比较两个字符串的指定长度。它的函数原型如下:

int strncmp(const char* str1, const char* str2, size_t n);
  • str1:要进行比较的第一个字符串。
  • str2:要进行比较的第二个字符串。
  • n:要比较的最大字符数。

strncmp 函数按照字典顺序比较 str1str2 的前 n 个字符。如果两个字符串相等返回 0,如果 str1 小于 str2 返回负数,如果 str1 大于 str2 返回正数。

下面是一个使用 strncmp 函数的示例:

#include <stdio.h>
#include <string.h>

int main() {
    char str1[] = "Hello";
    char str2[] = "Hey";
 
    int result = strncmp(str1, str2, 3);
 
    if (result == 0) {
        printf("Strings are equal.\n");
    } else if (result < 0) {
        printf("str1 is less than str2.\n");
    } else {
        printf("str1 is greater than str2.\n");
    }
 
    return 0;
}

上述代码比较了 str1str2 的前 3 个字符,并根据比较结果输出相应的信息。

5.3 strstr strtok

  1. strstr 函数:

strstr 函数用于在一个字符串中搜索另一个子字符串的出现位置。

函数原型:char *strstr(const char *str1, const char *str2)

  • 参数 str1 是要搜索的字符串,str2 是要查找的子字符串。
  • strstr 函数返回第一次出现 str2 的位置的指针,如果找不到则返回 NULL。

以下是一个代码示例:

#include <stdio.h>
#include <string.h>

int main() {
    char str1[] = "Hello, world!";
    char str2[] = "world";
    char *result;

    result = strstr(str1, str2);

    if (result != NULL) {
        printf("'%s' found at position %ld.\n", str2, result - str1);
    } else {
        printf("'%s' not found.\n", str2);
    }

    return 0;
}

输出结果为:'world' found at position 7.

  1. strtok 函数:

strtok 函数用于将一个字符串拆分成一系列子字符串,以指定的分隔符为准。

函数原型:char *strtok(char *str, const char *delim)

  • 参数 str 是要拆分的字符串
  • delim 是分隔符。

第一个参数又可以分为两种情况:

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

strtok 函数返回每次调用的子字符串指针,如果没有更多子字符串则返回 NULL,并在原字符串中用 ‘\0’ 替换分隔符。

以下是一个代码示例:

#include <stdio.h>
#include <string.h>

int main() {
    char str[] = "Hello, world! How are you today?";

    for (char* token = strtok(str, " ,!"); token != NULL; token = strtok(NULL, " ,!")) {
        printf("%s\n", token);
    }

    return 0;
}

输出结果为:
在这里插入图片描述

5.4 strerror perror

5.4.1 sterror

#include <stdio.h>
#include <string.h>
#include <errno.h>//必须包含的头文件
int main()
{
	FILE* pFile;
	pFile = fopen("unexist.ent", "r");
	if (pFile == NULL)
		printf("Error opening file unexist.ent: %s\n", strerror(errno));
	//errno: Last error number
	return 0;
}

errno 是 C 语言标准库中的一个全局变量,用于表示最近一次发生的错误代码。它在<errno.h> 头文件中定义。

在上述代码中,errno 用于获取 fopen 函数执行失败时的错误代码。strerror 函数用于将错误代码转换为对应的错误信息字符串。通过将 errno 作为参数传递给 strerror 函数,我们可以获取到错误原因的字符串。

如果文件打开失败,pFile 将为 NULL,并且通过使用 strerror(errno),你可以将错误信息打印到控制台。

5.4.2 perror

perror函数是 C 语言标准库中的一个函数,用于打印与当前 errno 值关联的错误信息。它的原型如下:

void perror(const char *s);

函数参数 s 是一个指向字符串的指针,表示自定义的错误前缀。当 perror 函数被调用时,它会根据当前的 errno 值,在标准错误流 stderr 中打印错误信息,格式为 “[前缀]: [错误信息]”

下面是一个简单的示例代码,演示了如何使用 perror 函数:

#include <stdio.h>
#include <errno.h>

int main() {
    FILE *file = fopen("nonexistent_file.txt", "r");
    if (file == NULL) {
        perror("Error");
        return errno;
    }
    
    // 文件操作...
    
    fclose(file);
    return 0;
}

在这个示例中,我们尝试打开一个不存在的文件。由于打开失败,file 的值为 NULL。接着使用 perror 函数打印错误信息,并返回 errno 值作为程序的退出码。

假设运行上述程序,输出可能类似于:

Error: No such file or directory

通过使用 perror,我们不仅能够打印出错误消息如 “No such file or directory”,而且还能自定义前缀,如 “Error”。

5.5 字符操作函数

函数如果他的参数符合下列条件就返回真
iscntrl任何控制字符
isspace空白字符:空格‘ ’,换页‘\f’,换行’\n’,回车‘\r’,制表符’\t’或者垂直制表符’\v’
isdigit十进制数字 0 ~ 9
isxdigit十六进制数字,包括所有十进制数字,小写字母 a ~ f,大写字母 A ~ F
islower小写字母 a ~ z
isupper大写字母 A ~ Z
isalpha字母 a ~ z 或 A ~ Z
isalnum字母或者数字,a ~ z, A ~ Z,0 ~ 9
ispunct标点符号,任何不属于数字或者字母的图形字符(可打印)
isgraph任何图形字符
isprint任何可打印字符,包括图形字符和空白字符

5.5 memcpy memmove memset memcmp

memcpymemmovememsetmemcmp这些函数都属于 <string.h> 头文件中的库函数。

  1. memcpy 函数用于将一个内存区域的内容复制到另一个内存区域。其原型如下:
void *memcpy(void *dest, const void *src, size_t n);

其中,dest 是目标内存区域的指针,src 是源内存区域的指针,n 是要复制的字节数。该函数会将源内存区域的内容复制到目标内存区域,并返回目标内存区域的指针。

下面是一个示例代码,演示了如何使用 memcpy 函数:

#include <stdio.h>
#include <string.h>

int main() {
    char source[] = "Hello, world!";
    char destination[20];
    memcpy(destination, source, strlen(source) + 1);
    printf("Copied string: %s\n", destination);
    return 0;
}

运行结果:

Copied string: Hello, world!
  1. memmove 函数也用于将一个内存区域的内容复制到另一个内存区域,但与 memcpy 不同,memmove 能够处理源内存区域和目标内存区域重叠的情况。其原型如下:
void *memmove(void *dest, const void *src, size_t n);

使用方法与 memcpy 类似。以下是一个示例代码:

#include <stdio.h>
#include <string.h>

int main() {
    char str[] = "12345";
    memmove(str + 2, str, strlen(str) + 1);
    printf("Moved string: %s\n", str);
    return 0;
}

运行结果:

Moved string: 1212345
  1. memset 函数用于将一块内存区域的所有字节设置为指定的值。其原型如下:
void *memset(void *s, int c, size_t n);

其中,s 是要设置的内存区域的起始地址,c 是要设置的值,n 是要设置的字节数。该函数会将指定内存区域的字节全部设置为指定值,并返回起始地址。

以下是一个示例代码,演示了如何使用 memset 函数:

#include <stdio.h>
#include <string.h>

int main() {
    char str[10];
    memset(str, 'A', sizeof(str));
    printf("Result: %s\n", str);
    return 0;
}

上述代码将字符数组 str 的所有字节都设置为 'A',并通过 printf 输出了结果。

  1. memcmp 函数用于比较两个内存区域的内容。其原型如下:
int memcmp(const void *s1, const void *s2, size_t n);

其中,s1s2 是要比较的两个内存区域的指针,n 是要比较的字节数。该函数以字节为单位逐个比较内存区域的内容

  • 如果比较的结果相等,返回值为 0;
  • 如果 s1 大于 s2,返回值大于 0;
  • 如果 s1 小于 s2,返回值小于 0。

以下是一个示例代码,演示了如何使用 memcmp 函数:

#include <stdio.h>
#include <string.h>

int main() {
    char str1[] = "Hello";
    char str2[] = "Hello";
    int result = memcmp(str1, str2, sizeof(str1));
    if (result == 0) {
        printf("Strings are equal.\n");
    } else {
        printf("Strings are not equal.\n");
    }
   return 0;
}

运行结果:

Strings are equal.
  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ice___Cpu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值