C/C++中的数组和数组的memset函数

01

数组介绍

什么是数组?

数组就是把相同数据类型的变量组合在一起而产生的数据集合。从数组定义中可以看出数组主要有两个方面:

  1. 相同数据类型的变量;
  2. 数据集合;

对于第一点比较好理解,对于第二点简单来说就是把这些相同数据类型的变量按某种关系联系起来,这也是数据结构的定义。因此数组可以说是一个存储数据的数据结构,这种关系就是这些相同数据类型的变量在内存中必须是连续存储的。

数组的定义

前面说数组是相同数据类型变量连续存储的集合,因此在定义数组的时候需要给定数组的数据类型以及数组存放的变量个数。因此数组的定义格式如下:

数据类型  数组名[数组大小];

这里简单介绍定义数组的三个部分:

  1. 数据类型:数据类型可以是四种基本数据类型,例如int、float、double、char以及bool等;
  2. 数组名:定义数组的名称,当然数组名除了表示该数组之外,还表示该数组的首地址;
  3. 数组大小:当定义数组的时候需要指定数组中相同数据类型变量的个数,因为定义变量的时候,会在内存中开辟一块空间,当定义单个变量的话很好理解,如果定义的是数组变量的话,如果不指定相同数据类型变量的个数,就不知道开辟多少块内存空间。需要注意这里的数组大小必须是常量,绝对不能是变量,因为通常情况下C语言是不允许对数组长度进行动态定义的;

例如定义一个数组:

int a[3];

上面定义了一个整型数组,数组名为a,此时数组a中有3个整型变量,由于一个整型变量占4个字节的内存空间,那么3个整型变量占3 * 4 = 12个字节的内存空间,并且这12个字节的空间是连续的。习惯把数组中的变量称为元素。

实际上面数组的定义是一维数组的定义。当然也会有二维数组、三维数组等多维数组,其本质都是一样的。二维数组可以理解成一维数组中的元素还是一维数组,同理三维等多维数组的理解。

二维数组定义格式如下:

数据类型  数组名[第一维大小][第二维大小];

数组的初始化

为了方便接介绍,接下来以一维数组为例,当然对于二维三维等更高维度的数组同样适用。数组的初始化可以使用下面三种方法实现:

  1. 定义数组时给所有元素赋初始值,这被称为"完全初始化";
  2. 只给一部分元素赋值,这被称为"不完全初始化";
  3. 只定义数组不对数组中的元素进行赋值,这被称为"完全不初始化"。

接下来分别介绍:

  • 定义数组时给所有元素赋初始值,这被称为"完全初始化"。简单理解:定义数组元素个数 = 赋予初值的元素个数。
#include <stdio.h>
int main(){
  int a[3] = {1, 2, 3}; // 完全初始化
  int i;
  // 打印输出数组
  for(i = 0; i < 3; i++){
    printf("a[%d] = %d\n", i, a[i]);
  }
  return 0;}

a[0] = 1 a[1] = 2 a[2] = 3

  • 只给一部分元素赋值,这被称为"不完全初始化"。简单理解:定义数组元素个数 < 赋予初值的元素个数。
#include <stdio.h>
int main(){
  int a[3] = {1, 2}; // 不完全初始化
  int i;
  // 打印输出数组
  for(i = 0; i < 3; i++){
    printf("a[%d] = %d\n", i, a[i]);
  }
  return 0;}

a[0] = 1 a[1] = 2 a[2] = 0

定义数组a中的3个元素时,只对第一个以及第二个元素值进行初始化,最后一个元素即a[2]没有被初始化。在"不完全初始化",没有被初始化的元素自动为0。

  • 只定义数组不对数组中的元素进行赋值,这被称为"完全不初始化"。
#include <stdio.h>
int main(){
  int a[3]; // 完全不初始化 
  int i;
  // 打印输出数组
  for(i = 0; i < 3; i++){
    printf("a[%d] = %d\n", i, a[i]);
  }
  return 0;}

a[0] = 21909 a[1] = 1871924176 a[2] = 32765

同样没有对数组中的元素初始化,但是最终和"不完全初始化"只对部分初始化的结果不同:

  1. 在对部分元素不进行初始化的"不完全初始化"中,未被初始化的元素被编译器自动赋值为0;
  2. 在对全部元素都不进行初始化的"完全不初始化"中,未被初始化的元素编译器自动赋值为比较大的随机数;

02

对数组中每个元素赋相同值的memset函数

在实际使用中可能需要对数组中的每一个元素赋以相同的值。当想要把整个数组元素都赋初值为0的话,可以使用"不完全初始化"的方式:

int a[3] = {0}; // 后面再写一些0也是可以的
int a[3] = {}; // 必须加上大括号,这样就和完全不初始化区分开

虽然上面的为数组每一个元素赋0初始值很方便,但是如果想要赋除0以外的其他初始值就需要使用其他方式。一般来说,给数组中每一个元素赋相同初始值的方法有两种:

  1. memset函数,这也是接下来重点介绍的方法;
  2. fill函数;

memset函数的格式为:

memset(数组名, 值, sizeof(数组名));

如果想要使用memset函数,需要在程序的开头添加string.h头文件。介绍memset函数是因为这个函数不是按照常规赋予一个初始值即可,memset函数使用的是按字节赋值,即对每个字节赋同样的值。

在计算机所有数值都是以二进制的方式进行存储的,这种二进制叫做机器数,这是计算机内部的数据表示形式,而在计算机中就是通过这些二进制来进行运算的。为了方便进行运算,机器数有三种常用的表示方法:

  1. 原码,人类比较容易理解和计算的机器数表示方式;
  2. 反码,人类不容易理解,可以看成是原码和补码之间进行转换的中间过程,如果想要知道对应的数值可以转换成人类容易理解的原码;
  3. 补码,人类不容易理解,计算机中所有运算都是采用补码来完成的,如果想要知道对应的数值也可以转换成人类容易理解的原码;

在计算机中原码、反码以及补码的转换规则如下:

  1. 正数的原码、反码和补码都是一样的;
  2. 负数的原码、反码和补码都是不一样的;
  3. 负数原码 --> 反码,符号位不变,数值位按位取反;
  4. 负数反码 --> 补码,符号位不变,数值位加1;

接下来看一看memset函数是如何按字节赋值:

#include <stdio.h>
#include <string.h> // 想要使用memset函数,必须加上string.h头文件
int main(){
  int i;
  int a[3] = {1, 2, 3}; 
  // 赋值为0
  memset(a, 0, sizeof(a));
  for(i = 0; i < 3; i++){
    printf("a[%d] = %d\n", i, a[i]);
  }
  // 赋值为-1
  memset(a, -1, sizeof(a));
  for(i = 0; i < 3; i++){
    printf("a[%d] = %d\n", i, a[i]);
  }
  return 0;}

a[0] = 0 a[1] = 0 a[2] = 0 a[0] = -1 a[1] = -1 a[2] = -1

在C/C++中int数据类型占4个字节,memset函数按字节赋值,memset函数中的值即为对字节赋值的数值。将字节赋值为0,0为正数因此原码、反码以及补码都是一样的,1个字节的0补码表示如下:

00000000

int有4个字节,每个字节都是0的补码:

00000000 00000000 00000000 00000000

上面得到的是4个字节的补码,由于符号位为0为正值,正数反码补码以及原码都是一样的,转换成原码结果为0。即0就是数组需要为每个元素赋的值。

而对于-1而言,-1在1个字节中的原码表示为:

10000001 -1在1个字节中的原码表示方式

在计算机中参与运算的都是补码,因此还需将上面的原码转换成补码:

10000001 -1在1个字节中的原码表示方式 11111110 -1在1个字节中的反码表示方式 11111111 -1在1个字节中的补码表示方式

int有4个字节,每个字节都是-1的补码:

11111111 11111111 11111111 11111111

上面得到的是4个字节的补码,由于符号位为-为负值,正数反码补码以及原码是不一样的,补码转换成原码:

  1. 补码 --> 反码,符号位不变,减1;
  2. 反码 --> 原码,符号位不变,按位取反;

11111111 11111111 11111111 11111111 补码 11111111 11111111 11111111 11111110 反码(减1) 10000000 00000000 00000000 00000001 原码(按位取反),结果为-1

最终将数组a中的每一个元素都赋值为-1。需要注意,转换为4个字节的时候,最左边的最高位依然是符号位。

接下来,为一个字节赋值为-121进行分析(只要数值能够在一个字节范围中即可,超出会抛出异常):

由于-121是负数,因此需要计算转换成补码:

11111001 -121原码 10000110 -121反码 10000111 -121补码

上面只是1个字节,对于int数据类型的4个字节结果为:

10000111 10000111 10000111 10000111 补码形式

现在并不知道上面的补码具体是什么,接下来可以将上面的补码转换成原码来看看具体的数值。

10000111 10000111 10000111 10000111 补码形式 10000111 10000111 10000111 10000110 反码形式(减1) 11111000 01111000 01111000 01111001 原码形式(按位取反,最左边是符号位,符号位不要变)

通过计算,上面原码值为-2021161081‬。有了结果,通过代码来验证。

#include <stdio.h>
#include <string.h> // 想要使用memset函数,必须加上string.h头文件
int main(){
  int i;
  int a[3] = {1, 2, 3}; 
  // 赋值为-1
  memset(a, -121, sizeof(a));
  for(i = 0; i < 3; i++){
    printf("a[%d] = %d\n", i, a[i]);
  }
  return 0;}

a[0] = -2021161081 a[1] = -2021161081 a[2] = -2021161081

实验的结果和我们分析结果一致。这种按字节赋值的方式还是比较复杂的,因此还有一种fill方法,这里不详细介绍。这种方式赋值虽然比较复杂,但是效率比较高。

本文分享自微信公众号 - AI机器学习与深度学习算法(AI-KangChen)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-10-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏曲水流觞TechRill

浅谈NIO

说到NIO大家都不会陌生,它是JDK中提供的IO工具集。 它又被称作为New I/O或Non Blocking I/O。相较于传统面向流的java.io,nio...

11420
来自专栏曲水流觞TechRill

OrientDB图遍历SQL之TRAVERSE

本文介绍的TRAVERSE语法是基于OrientDB3.0.x版本,所有的SQL在OrientDB3.0.4社区版本自带的数据库demodb下试验,数据模型请参...

18630
来自专栏cwl_Java

软考分类精讲-软件架构设计(一)

8610
来自专栏cwl_Java

软考分类精讲-系统可靠性分析与设计

9320
来自专栏dotNET匠人的技术自留地

C#刷遍Leetcode面试题系列连载(4): No.633 - 平方数之和

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

8050
来自专栏中科院渣渣博肆僧一枚

文件结束符EOF

人们经常误认为 EOF 是从文件中读取的一个字符(牢记)。其实,EOF 不是一个字符,它被定义为是 int 类型的一个负数(比如 -1)。EOF 也不是文件中实...

9210
来自专栏cwl_Java

2014年系统架构师软考案例分析考点

6420
来自专栏cwl_Java

2017年系统架构师软考案例分析考点

判断以下哪些处于有状态,哪些处于无状态,有状态:会发生状态改变,无状态:不会发生状态改变

13120
来自专栏cwl_Java

2009年系统架构师软考考点:案例分析

6820
来自专栏CU技术社区

243 张图片为你解析 Linux 轻量级自动运维化工具 Ansible

基于Python语言研发,由Paramiko, PyYAML和Jinja2三个核心库实现;

6920

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励