精华内容
下载资源
问答
  • 生成固定长度不重复随机字符串

    千次阅读 2018-02-11 13:29:38
    问题来自V2EX:如何生成固定长度唯一随机字符串? 不过需求后来有修改过,原始的需求略有不同,所以我的回答与现在的问题太对得上,这里以原需求为基础重新提出这个问题,并且加上一些更有代表性的条件。 条件有...

    问题

    问题来自V2EX:如何生成固定长度唯一随机字符串?

    不过需求后来有修改过,原始的需求略有不同,所以我的回答与现在的问题不太对得上,这里以原需求为基础重新提出这个问题,并且加上一些更有代表性的条件。

    条件有以下几个:

    • 串长固定为8位,大小写字母加数字组合
    • 串内容随机,不可被猜测,不可重复
    • 目标数据量不超过一千万
    • 不需要数据库

    分析

    其实重点在不需要数据库,否则直接用一个随机数生成器生成一千万个随机串放到数据库里直接用就好了。

    如果不依赖数据库的话,用任何随机数生成器,都不能保证绝对不重复——虽然概率非常低,比如8位大小写字母数字组合的空间超过14位十进制数,一千万不超过7位十进制数,即低于千万分之一——但理论上仍然可能发生。

    要保持唯一就需要自增ID,但是如果只是简单地用自增ID又会导致可能被猜测,虽然可以把组合逻辑搞得复杂一点,但数据量大了还是有一定的可能性被猜出——不要对自己想的简单逻辑有太高的自信是一个很重要的安全原则。

    实现

    基本如我在评论里说的那方法:

    • 8位大小写字母加数字转成二进制大概是47位多,一千万转成二进制不到27位
    • 所以可以用一千万以内的自增ID,加上20位二进制的随机数,组合成一个47位的二进制数
    • 因为上面的组合是可猜测的,所以需要进行一次加密,用一个自定义密码对这个数进行RC4加密
    • 最后用BASE62转换为大小写字母加数字的字符串

    恢复ID的方法:

    • 把字符串转成47位二进制数
    • 用RC4解密
    • 去掉随机数部分,转为整数即为原始ID

    关键点:

    • 随机数与ID的组合方式,这个与加密算法是相关的
    • 加密算法的选择,必须适合这样的需求,即消除可猜测性

    综合这两点,加密算法的选择范围就不大了,大部分常用的分组加密算法(一般都是128位分组,老一些的TEA也是64位,即使DES也需要56位)都肯定是没办法用的,只能用弱一些的短分组加密算法,比如以字节为单位进行加密的RC4。

    但是因为47位不是刚好整数字节,所以要分成40位和7位两组,其中40位由27位ID与13位随机数分散组合后进行RC4加密,再加7位随机数组成47位二进制数。

    代码

    初始化参数包括:

    • key: RC4加密密钥
    • chars: 目标字符串可用字符集合,不可重复
    • length: 目标字符串长度,目标字符串转为二进制不超过64位无符号整数,即: log(pow(len(chars), length)) / log(2) <= 64
    • bits_id: ID的二进制位数,不能超过目标串转为二进制位数除以8取整后乘以7的值,即: bits_id < int(log(pow(len(chars), length)) / log(2) / 8) * 7,之所以要如此,是为了保证加密的每个字节至少有一位随机数,确保RC4加密后仍然不可猜测
    • secure_level: 取值0-4,为每个字节中随机数的位数,越多越安全,0则不含随机数,超过4位则意义不大,仅作初始化时检查之用,如果目标串长度设置不符合这个要求会报异常

    主要功能函数是两个:

    • encrypt: ID转目标串
    • decrypt: 字符串转ID

    完整代码在GayHub

    之所以有兴趣研究这个东西,是因为这种需求其实还挺有用的。比如与微信或支付宝之类的支付平台对接时都需要一个唯一的订单号,但是通常客户又不希望把自己的内部订单ID曝露在外,所以都需要转换一下,当然这类应用都有数据库,只要生成一个随机串再查一下数据库有没有重复即可。还有像短链接应用也是可以用的(用七位字符就可以支持到41位二进制ID,secure_level=0)。

    然而毕竟还是要多查一次数据库,这种事情让CPU来干可能会更省资源一些。

    展开全文
  • 最近由于公司有个业务需要生成30多万个优惠券编码长度在6-8,网上百度了许多方法发现都适用此场景。常规方法生成数据库循环对比太耗性能,技术小白想不出什么高深写法,就自己简单写了一个完美解决需求。(方法...

    ThinkPHP生成唯一优惠券编码

    最近由于公司有个业务需要生成30多万个优惠券编码长度在6-8位,网上百度了许多方法发现都不适用此场景。常规方法生成数据库循环对比太耗性能,技术小白想不出什么高深写法,就自己简单写了一个完美解决需求。(方法还有待优化,请大神多多指点)贴上代码。

    把字母数字随机组合成三个数组,然后根据数据库存的自增id的每个数字作为下标去取字符,拼接成需要的编码。各位数
    字最大是9,所以只能取到下标为9的,数组设定为十个字符。
    1.先获取数据库存的最后一条id如果没有从1开始。
    2.判断id长度与传来的编码长度是否一致,如果小于传来的编码长度时根据id每个位数的数字分别取到字符,剩余的调用随机字符拼接。如果等于传来的编码长度直接全部获取
    3.参数定义
    $post[‘num’] 需要生成的数量
    $post[‘length’]需要生成的优惠券编码长度
    $post[‘type’]需要使用的编码数组
    我这里还有额外参数是我数据库存的优惠券id

     	/**
         * [index 自动生成优惠券编码]
         * @return [type] [description]
         */
        public function index()
        {
            $coupon = new ShopCoupon();
            $record = new ShopCouponRecord();
           
                $post = request()->post();
                // 检测生成数量
                if(!isset($post['num']) || $post['num'] == '' || $post['num'] == 0 || $post['num'] > 10000){
                    $this->error('数量错误');
                }
                // 检测优惠券码长度
                if(!isset($post['length']) || $post['length'] == '' || $post['length'] < 6 || $post['length'] > 8){
                    $this->error('优惠券码长度错误');
                }
                // 检测用是否选择优惠券
                if (!isset($post['coupon_id']) || $post['coupon_id'] == '' || $post['coupon_id'] < 0) {
                    $this->error('请选择优惠券');
                }
                // 检测是否传来编码类型
                if (isset($post['type']) && $post['type'] != '') {
                    $type = $post['type'];
                } else {
                    $type = 1;
                }
                // 编码数组
                if ($type == 1) {
                    $arr = ['F', 'J', 9, 'R', 6, 'Q', 'S', 3, 'M', 'C'];
                } elseif ($type == 2) {
                    $arr = ['W', 'X', 'Y', 1, 'K', 'O', 'L', 8, 'G', 'B'];
                } else {
                    $arr = ['Z', 4, 'D', 'T', 2, 'H', 7, 'U', 'P', 5];
                }
                // 自定义一个优惠券编码记录id
                $coupon_record_id = 1;
                // 根据优惠券id获取优惠券是否已经生成过编码
                $record_data = $record->order('id desc')->find();
                if ($record_data) {
                    $coupon_record_id = $record_data['id'] + 1;
                }
                $data = [];
                for ($iiii = 0; $iiii < $post['num']; $iiii++) {
                    // 检测优惠券编码id长度是否大于1
                    if (mb_strlen($coupon_record_id) > 1) {
                        // 判断优惠券编码id长度是否与传来的编码长度一致
                        if (mb_strlen($coupon_record_id) == $post['length']) {
                            $coupon_no = '';
                            // 如果长度等于优惠券长度 优惠券编码id转成数组
                            $coupon_record_id_arr = str_split($coupon_record_id);
                            for ($ii = 0; $ii < count($coupon_record_id_arr); $ii++) {
                                $coupon_no .= $arr[$coupon_record_id_arr[$ii]];
                            }
                        } else {
                            // 优惠券编码id长度小于编码长度 需要判断查了几位
                            $coupon_no = '';
                            // 如果长度等于优惠券长度 优惠券编码id转成数组
                            $coupon_record_id_arr = str_split($coupon_record_id);
                            for ($iiiiii = 0; $iiiiii < count($coupon_record_id_arr); $iiiiii++) {
                                $coupon_no .= $arr[$coupon_record_id_arr[$iiiiii]];
                            }
                            $coupon_no .= $this->getString($post['length'] - mb_strlen($coupon_record_id), $type);
                        }
                    } else {
                        // 优惠券编码id长度小于编码长度 需要判断查了几位
                        $coupon_no = '';
                        $coupon_no .= $arr[$coupon_record_id];
                        $coupon_no .= $this->getString($post['length'] - 1, $type);
                    }
                    $data[$iiii]['coupon_id'] = $post['coupon_id'];
                    $data[$iiii]['coupon_no'] = $coupon_no;
                    $data[$iiii]['create_time'] = time();
    
                    $coupon_record_id += 1;
                }
                $re = $record->saveAll($data);
                if ($re) {
                    $this->success('ok', $re);
                } else {
                    $this->error('NO');
                }
        }
    
        /**
         * [getString 获取随机字符串]
         * @param int $lent 字符串长度
         * @return [type] [description]
         */
        public function getString($lent = 0, $type = 1)
        {
            if ($lent <= 0) {
                return false;
            }
            if ($type == 1) {
                $string = 'AZWXEDVTGBYHNUIKOLP';
    
            } elseif ($type == 2) {
                $string = 'QAZSEDCRFVTHNUJMIP';
            } else {
                $string = 'QAWSXECRFVGBYNJMIKOL';
            }
            $str = '';
            for ($i = 0; $i < $lent; $i++) {
                $index = rand(0, strlen($string) - 1);
                $str .= $string[$index];
            }
            return $str;
        }
    

    注意:

    1. 编码数组和随机字符串是相互对应的,字母出现在编码数组中之后下面随机字符串就不能出现(如果出现就会重复)。
    2. 编码数组字母位置定义好之后不要改动。

    缺点:

    1. 编码长度越长生成的越多,当你需要六位编码时最多生成999999个,需要四位大量编码慎用

    附上我生成的截图 (一次五万个大约30秒左右)
    数据库保存的优惠券编码

    展开全文
  • 在JavaScript中生成随机字符串/字符

    千次阅读 2019-12-07 11:30:01
    我想要一个由从[a-zA-Z0-9]随机挑选的字符组成的5个字符串。 用JavaScript做到这一点的最佳方法是什么?

    我想要一个由从[a-zA-Z0-9]随机挑选的字符组成的5个字符串。

    JavaScript做到这一点的最佳方法是什么?


    #1楼

    我认为这将为您工作:

     function makeid(length) { var result = ''; var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; var charactersLength = characters.length; for ( var i = 0; i < length; i++ ) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); } return result; } console.log(makeid(5)); 


    #2楼

    这样的事情应该工作

    function randomString(len, charSet) {
        charSet = charSet || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
        var randomString = '';
        for (var i = 0; i < len; i++) {
            var randomPoz = Math.floor(Math.random() * charSet.length);
            randomString += charSet.substring(randomPoz,randomPoz+1);
        }
        return randomString;
    }
    

    使用默认字符集[a-zA-Z0-9]调用或发送自己的字符集:

    var randomValue = randomString(5);
    
    var randomValue = randomString(5, 'PICKCHARSFROMTHISSET');
    

    #3楼

     function randomstring(L) { var s = ''; var randomchar = function() { var n = Math.floor(Math.random() * 62); if (n < 10) return n; //1-10 if (n < 36) return String.fromCharCode(n + 55); //AZ return String.fromCharCode(n + 61); //az } while (s.length < L) s += randomchar(); return s; } console.log(randomstring(5)); 


    #4楼

    function randomString (strLength, charSet) {
        var result = [];
    
        strLength = strLength || 5;
        charSet = charSet || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    
        while (--strLength) {
            result.push(charSet.charAt(Math.floor(Math.random() * charSet.length)));
        }
    
        return result.join('');
    }
    

    这将是干净的。 http://jsperf.com/ay-random-string也很快。


    #5楼

    生成10个字符长的字符串。 长度由参数设置(默认为10)。

    function random_string_generator(len) {
    var len = len || 10;
    var str = '';
    var i = 0;
    
    for(i=0; i<len; i++) {
        switch(Math.floor(Math.random()*3+1)) {
            case 1: // digit
                str += (Math.floor(Math.random()*9)).toString();
            break;
    
            case 2: // small letter
                str += String.fromCharCode(Math.floor(Math.random()*26) + 97); //'a'.charCodeAt(0));
            break;
    
            case 3: // big letter
                str += String.fromCharCode(Math.floor(Math.random()*26) + 65); //'A'.charCodeAt(0));
            break;
    
            default:
            break;
        }
    }
    return str;
    }
    

    #6楼

    如果有人对一次性分配内存(虽然为了方便起见而没有格式化)感兴趣(但请注意,对于小字符串,这实际上没关系),这是如何做的:

    Array.apply(0, Array(5)).map(function() {
        return (function(charset){
            return charset.charAt(Math.floor(Math.random() * charset.length))
        }('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'));
    }).join('')
    

    您可以将5替换为所需字符串的长度。 感谢这篇文章中的@AriyaHidayat提供了针对map函数不能在Array(5)创建的稀疏数组map运行的解决方案。


    #7楼

    这个紧凑的小把戏怎么样?

    var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    var stringLength = 5;
    
    function pickRandom() {
        return possible[Math.floor(Math.random() * possible.length)];
    }
    
    var randomString = Array.apply(null, Array(stringLength)).map(pickRandom).join('');
    

    您需要在那里使用Array.apply来将空数组诱骗为未定义的数组。

    如果您要为ES2015编码,则构建数组会稍微简单一些:

    var randomString = Array.from({ length: stringLength }, pickRandom).join('');
    

    #8楼

    这肯定可以工作

    <script language="javascript" type="text/javascript">
    function randomString() {
     var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
     var string_length = 8;
     var randomstring = '';
     for (var i=0; i<string_length; i++) {
      var rnum = Math.floor(Math.random() * chars.length);
      randomstring += chars.substring(rnum,rnum+1);
     }
     document.randform.randomfield.value = randomstring;
    }
    </script>
    

    #9楼

    这是我创建的方法。
    它将创建一个包含大写和小写字符的字符串。
    另外,我还包含了将创建字母数字字符串的函数。

    工作示例:
    http://jsfiddle.net/greatbigmassive/vhsxs/ (仅限Alpha)
    http://jsfiddle.net/greatbigmassive/PJwg8/ (字母数字)

    function randString(x){
        var s = "";
        while(s.length<x&&x>0){
            var r = Math.random();
            s+= String.fromCharCode(Math.floor(r*26) + (r>0.5?97:65));
        }
        return s;
    }
    

    2015年7月升级
    这做同样的事情,但是更有意义,并且包括所有字母。

    var s = "";
    while(s.length<x&&x>0){
        v = Math.random()<0.5?32:0;
        s += String.fromCharCode(Math.round(Math.random()*((122-v)-(97-v))+(97-v)));
    }
    

    #10楼

    这是doubletap出色答案的改进。 原始文件有两个缺点,在这里解决:

    首先,正如其他人提到的那样,它产生短字符串甚至空字符串(如果随机数为0)的可能性很小,这可能会破坏您的应用程序。 这是一个解决方案:

    (Math.random().toString(36)+'00000000000000000').slice(2, N+2)
    

    其次,上述原始方法和解决方案都将字符串大小N限制为16个字符。 下面的代码将为任何N返回一个大小为N的字符串(但请注意,使用N> 16不会增加随机性或减少碰撞的可能性):

    Array(N+1).join((Math.random().toString(36)+'00000000000000000').slice(2, 18)).slice(0, N)
    

    说明:

    1. 选择[0,1)范围内的随机数,即介于0(含)和1(不含)之间。
    2. 将数字转换为以36为基数的字符串,即使用字符0-9和az。
    3. 用零填充(解决第一个问题)。
    4. 删除开头的“ 0”。 前缀和额外的填充零。
    5. 重复该字符串足够的次数,以使其至少包含N个字符(通过将空字符串与较短的随机字符串用作分隔符进行连接)。
    6. 从字符串中精确切出N个字符。

    进一步的想法:

    • 此解决方案不使用大写字母,但在几乎所有情况下(无双关语)都无关紧要。
    • 原始答案中N = 16时的最大字符串长度是在Chrome中测量的。 在Firefox中,它的N =11。但是,如前所述,第二种解决方案是支持任何请求的字符串长度,而不是增加随机性,因此没有太大区别。
    • 至少就Math.random()返回的结果均匀分布而言,所有返回的字符串都有相等的返回概率(无论如何,这不是密码强度随机性)。
    • 并非所有可能的大小为N的字符串都将返回。 在第二种解决方案中,这是显而易见的(因为只复制了较小的字符串),但在原始答案中也是如此,因为在转换为base-36时,最后几位可能不是原始随机位的一部分。 具体来说,如果您查看Math.random()。toString(36)的结果,您会注意到最后一个字符分布不均匀。 同样,在几乎所有情况下都没有关系,但是我们从随机字符串的开头而不是结尾处切片最后一个字符串,这样短字符串(例如N = 1)不会受到影响。

    更新:

    这是我想到的其他几个功能样式的单线纸。 它们与上述解决方案的不同之处在于:

    • 他们使用一个明确的任意字母(更通用,并且适用于要求同时使用大写和小写字母的原始问题)。
    • 所有长度为N的字符串都有相等的返回概率(即字符串不包含重复)。
    • 它们基于映射函数,而不是toString(36)技巧,这使它们更加直接和易于理解。

    因此,假设您选择的字母是

    var s = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    

    然后这两个彼此相等,因此您可以选择对您而言更直观的一个:

    Array(N).join().split(',').map(function() { return s.charAt(Math.floor(Math.random() * s.length)); }).join('');
    

    Array.apply(null, Array(N)).map(function() { return s.charAt(Math.floor(Math.random() * s.length)); }).join('');
    

    编辑:

    好像qubyteMartijn de Milliano提出了与后者类似的解决方案(赞!),我以某种方式错过了。 由于它们看起来不那么短,所以我还是把它留在这里,以防有人真的想要单线:-)

    同样,在所有解决方案中都将“ new Array”替换为“ Array”以节省更多字节。


    #11楼

    这是一些简单的衬板。 更改new Array(5)以设置长度。

    包括0-9a-z

    new Array(5).join().replace(/(.|$)/g, function(){return ((Math.random()*36)|0).toString(36);})
    

    包括0-9a-zA-Z

    new Array(5).join().replace(/(.|$)/g, function(){return ((Math.random()*36)|0).toString(36)[Math.random()<.5?"toString":"toUpperCase"]();});
    

    #12楼

    这是#1答案的测试脚本(谢谢@ csharptest.net)

    该脚本运行makeid() 1 million次,并且您可以看到5 makeid()非常独特的。 以10个字符长度运行它是非常可靠的。 我已经运行了大约50次,还没有看到重复的内容:-)

    注意:节点堆栈大小限制超过了400万左右,因此您无法运行500万次,因为它将永远无法完成。

    function makeid()
    {
        var text = "";
        var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    
        for( var i=0; i < 5; i++ )
            text += possible.charAt(Math.floor(Math.random() * possible.length));
    
        return text;
    }
    
    ids ={}
    count = 0
    for (var i = 0; i < 1000000; i++) {
        tempId = makeid();
        if (typeof ids[tempId] !== 'undefined') {
            ids[tempId]++;
            if (ids[tempId] === 2) {
                count ++;
            }
            count++;
        }else{
            ids[tempId] = 1;
        }
    }
    console.log("there are "+count+ ' duplicate ids');
    

    #13楼

    Math.random对这种事情不利

    选项1

    如果您能够在服务器端执行此操作,则只需使用crypto模块-

    var crypto = require("crypto");
    var id = crypto.randomBytes(20).toString('hex');
    
    // "bb5dc8842ca31d4603d6aa11448d1654"
    

    结果字符串将是您生成的随机字节的两倍长; 编码为十六进制的每个字节均为2个字符。 20个字节将是十六进制的40个字符。


    选项2

    如果您必须在客户端执行此操作,则可以尝试使用uuid模块-

    var uuid = require("uuid");
    var id = uuid.v4();
    
    // "110ec58a-a0f2-4ac4-8393-c866d813b8d1"
    

    选项3

    如果您必须在客户端执行此操作,而不必支持旧版浏览器,则可以在没有依赖关系的情况下进行操作-

     // dec2hex :: Integer -> String // ie 0-255 -> '00'-'ff' function dec2hex (dec) { return ('0' + dec.toString(16)).substr(-2) } // generateId :: Integer -> String function generateId (len) { var arr = new Uint8Array((len || 40) / 2) window.crypto.getRandomValues(arr) return Array.from(arr, dec2hex).join('') } console.log(generateId()) // "82defcf324571e70b0521d79cce2bf3fffccd69" console.log(generateId(20)) // "c1a050a4cd1556948d41" 


    有关crypto.getRandomValues更多信息-

    crypto.getRandomValues()方法可让您获得加密强度高的随机值。 作为参数给出的数组填充有随机数(其密码学含义是随机的)。

    这是一个小的控制台示例-

    > var arr = new Uint8Array(4) # make array of 4 bytes (values 0-255)
    > arr
    Uint8Array(4) [ 0, 0, 0, 0 ]
    
    > window.crypto
    Crypto { subtle: SubtleCrypto }
    
    > window.crypto.getRandomValues()
    TypeError: Crypto.getRandomValues requires at least 1 argument, but only 0 were passed
    
    > window.crypto.getRandomValues(arr)
    Uint8Array(4) [ 235, 229, 94, 228 ]
    

    对于IE11支持,您可以使用-

    (window.crypto || window.msCrypto).getRandomValues(arr)
    

    有关浏览器的覆盖范围,请参见https://caniuse.com/#feat=getrandomvalues


    #14楼

    随机字符串生成器(字母数字|字母|数字)

    /**
     * RANDOM STRING GENERATOR
     *
     * Info:      http://stackoverflow.com/a/27872144/383904
     * Use:       randomString(length [,"A"] [,"N"] );
     * Default:   return a random alpha-numeric string
     * Arguments: If you use the optional "A", "N" flags:
     *            "A" (Alpha flag)   return random a-Z string
     *            "N" (Numeric flag) return random 0-9 string
     */
    function randomString(len, an){
        an = an&&an.toLowerCase();
        var str="", i=0, min=an=="a"?10:0, max=an=="n"?10:62;
        for(;i++<len;){
          var r = Math.random()*(max-min)+min <<0;
          str += String.fromCharCode(r+=r>9?r<36?55:61:48);
        }
        return str;
    }
    
    randomString(10);        // "4Z8iNQag9v"
    randomString(10, "A");   // "aUkZuHNcWw"
    randomString(10, "N");   // "9055739230"
    

    玩得开心。 jsBin演示


    尽管上面对所需的(A / N,A,N)输出使用了额外的检查,但是为了更好地理解我们将其分解为基本内容(仅字母数字)。

    • 创建一个接受参数的函数(随机String结果的期望长度)
    • 创建一个空字符串,如var str = ""; 连接随机字符
    • 在循环内创建0到61 rand索引号 (0..9 + A..Z + a..z = 62)
    • 创建一个条件逻辑调整/固定rand (因为它是0..61),将其递增某个数字(请参见下面的示例),以获取正确的CharCode号和相关的Character。
    • 在循环内连接一个str String.fromCharCode( incremented rand )

    让我们描绘一下Character表及其范围

    _____0....9______A..........Z______a..........z___________  Character
         | 10 |      |    26    |      |    26    |             Tot = 62 characters
        48....57    65..........90    97..........122           CharCode ranges
    

    Math.floor( Math.random * 62 )给出的范围是0..61 (我们需要)。 如何修正(增加)随机数以获得正确的charCode范围

          |   rand   | charCode |  (0..61)rand += fix            = charCode ranges |
    ------+----------+----------+--------------------------------+-----------------+
    0..9  |   0..9   |  48..57  |  rand += 48                    =     48..57      |
    A..Z  |  10..35  |  65..90  |  rand += 55 /*  90-35 = 55 */  =     65..90      |
    a..z  |  36..61  |  97..122 |  rand += 61 /* 122-61 = 61 */  =     97..122     |
    

    上表中的条件运算逻辑

       rand += rand>9 ? ( rand<36 ? 55 : 61 ) : 48 ;
    // rand +=  true  ? (  true   ? 55 else 61 ) else 48 ;
    

    如果按照上述说明进行操作,则应该可以创建以下字母数字代码段

    jsBin演示

    function randomString( len ) {
      var str = "";                                         // String result
      for(var i=0; i<len; i++){                             // Loop `len` times
        var rand = Math.floor( Math.random() * 62 );        // random: 0..61
        var charCode = rand+= rand>9? (rand<36?55:61) : 48; // Get correct charCode
        str += String.fromCharCode( charCode );             // add Character to str
      }
      return str;       // After all loops are done, return the concatenated string
    }
    
    console.log( randomString(10) ); // "7GL9F0ne6t"
    

    或者,如果您会:

    function randomString( n ) {
      var r="";
      while(n--)r+=String.fromCharCode((r=Math.random()*62|0,r+=r>9?(r<36?55:61):48));
      return r;
    }
    

    #15楼

    最简单的方法是:

    (new Date%9e6).toString(36)
    

    这会根据当前时间生成5个字符的随机字符串。 示例输出为4mtxj4mv904mwp1

    问题是,如果您在同一秒两次调用它,它将生成相同的字符串。

    比较安全的方法是:

    (0|Math.random()*9e6).toString(36)
    

    这将生成一个随机的4或5个字符的字符串,始终不同。 输出示例如30jzm1r5914su1a

    无论哪种方式,第一部分都会生成一个随机数。 .toString(36)部分将数字转换为它的base36(字母十进制)表示形式。


    #16楼

    假设您使用underscorejs,则可以仅在两行中优雅地生成随机字符串:

    var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    var random = _.sample(possible, 5).join('');
    

    #17楼

    最紧凑的解决方案,因为slicesubstring短。 从字符串的末尾减去可以避免由random函数生成的浮点符号:

    Math.random().toString(36).slice(-5);
    

    甚至

    (+new Date).toString(36).slice(-5);
    

    更新:添加了另一种使用btoa方法的方法:

    btoa(Math.random()).slice(0, 5);
    btoa(+new Date).slice(-7, -2);
    btoa(+new Date).substr(-7, 5);
    

     // Using Math.random and Base 36: console.log(Math.random().toString(36).slice(-5)); // Using new Date and Base 36: console.log((+new Date).toString(36).slice(-5)); // Using Math.random and Base 64 (btoa): console.log(btoa(Math.random()).slice(0, 5)); // Using new Date and Base 64 (btoa): console.log(btoa(+new Date).slice(-7, -2)); console.log(btoa(+new Date).substr(-7, 5)); 


    #18楼

    如果你正在使用Lodash下划线 ,那么就这么简单:

    var randomVal = _.sample('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', 5).join('');
    

    #19楼

    简短,简单且可靠

    返回恰好5个随机字符,与此处找到的一些评价最高的答案相反。

    Math.random().toString(36).substr(2, 5);
    

    #20楼

    快速改进的算法。 不保证统一(见评论)。

    function getRandomId(length) {
        if (!length) {
            return '';
        }
    
        const possible =
            'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
        let result = '';
        let array;
    
        if ('Uint8Array' in self && 'crypto' in self && length <= 65536) {
            array = new Uint8Array(length);
            self.crypto.getRandomValues(array);
        } else {
            array = new Array(length);
    
            for (let i = 0; i < length; i++) {
                array[i] = Math.floor(Math.random() * 62);
            }
        }
    
        for (let i = 0; i < length; i++) {
            result += possible.charAt(array[i] % 62);
        }
    
        return result;
    }
    

    #21楼

    您可以使用coderain 。 这是一个根据给定模式生成随机代码的库。 将#用作大写和小写字符以及数字的占位符:

    var cr = new CodeRain("#####");
    console.log(cr.next());
    

    还有其他占位符,例如A代表大写字母或9代表数字。

    可能有用的是,调用.next()总是会给您带来独特的结果,因此您不必担心重复。

    这是一个演示应用程序,它生成唯一的随机代码列表

    全面披露:我是coderain的作者。


    #22楼

    回答“我需要随机字符串”问题(无论使用哪种语言)的问题实际上是每种解决方案都使用有缺陷的字符串长度基本规范。 问题本身很少揭示为什么需要随机字符串,但是我会挑战您,很少需要长度为8的随机字符串。您总是需要一定数量的唯一字符串 ,例如,出于某些目的用作标识符。

    获得严格唯一的字符串有两种主要方法:确定性(不是随机的)和存储/比较(繁琐的)。 我们做什么? 我们放弃了幽灵。 我们代之以概率唯一性 。 也就是说,我们接受我们的字符串存在唯一性的风险(但很小)。 在这里,了解碰撞概率很有帮助。

    因此,我将不变的需求重新表述为需要一定数量的字符串,重复的风险很小。 举一个具体的例子,假设您要生成500万个ID。 您不想存储和比较每个新字符串,并且希望它们是随机的,因此您会承受重复的风险。 例如,假设万亿重覆机会的风险小于1。 那么,您需要多长的字符串? 嗯,这个问题没有明确说明,因为它取决于所使用的字符。 但更重要的是,它被误导了。 您需要的是字符串熵的规范,而不是字符串的长度。 熵可以直接与某些字符串中重复的概率相关。 字符串长度不能。

    这就是像EntropyString这样的库可以提供帮助的地方。 使用entropy-string在500万个字符串中重复概率小于1万亿的随机ID:

    import {Random, Entropy} from 'entropy-string'
    
    const random = new Random()
    const bits = Entropy.bits(5e6, 1e12)
    
    const string = random.string(bits)
    

    “ 44hTNghjNHGGRHqH9”

    entropy-string默认使用32个字符的字符集。 还有其他预定义的字符集,您也可以指定自己的字符。 例如,生成具有与上述相同的熵但使用十六进制字符的ID:

    import {Random, Entropy, charSet16} from './entropy-string'
    
    const random = new Random(charSet16)
    const bits = Entropy.bits(5e6, 1e12)
    
    const string = random.string(bits)
    

    “ 27b33372ade513715481f”

    请注意,由于使用的字符集中的字符总数不同,字符串长度也有所不同。 在指定数量的潜在字符串中重复的风险是相同的。 字符串长度不是。 最重要的是,重复的风险和潜在的字符串数是明确的。 不再需要猜测字符串的长度。


    #23楼

    这样的事情怎么样: Date.now().toString(36)不是很随机,但是每次调用时都很短而且很独特。


    #24楼

    带有es6 传播算子的较新版本:

    [...Array(30)].map(() => Math.random().toString(36)[2]).join('')

    • 30是任意数字,您可以选择所需的任何令牌长度
    • 36是您可以传递给numeric.toString()的最大基数,表示所有数字和小写字母az
    • 2用于从如下所示的随机字符串中选择第二个数字: "0.mfbiohx64i" ,我们可以在0.之后获取任何索引0.

    #25楼

    我找不到同时支持小写和大写字符的干净解决方案。

    仅支持小写字母:

    Math.random().toString(36).substr(2, 5)

    基于该解决方案来支持小写和大写:

    Math.random().toString(36).substr(2, 5).split('').map(c => Math.random() < 0.5 ? c.toUpperCase() : c).join('');

    更改5substr(2, 5)调整到你需要的长度。


    #26楼

    为了满足要求[a-zA-Z0-9]并且长度= 5,请使用

    btoa(Math.random()).substr(5, 5);
    

    将出现小写字母,大写字母和数字。


    #27楼

    不区分大小写的字母数字字符:

     function randStr(len) { let s = ''; while (s.length < len) s += Math.random().toString(36).substr(2, len - s.length); return s; } // usage console.log(randStr(50)); 

    此功能的好处是您可以获取不同长度的随机字符串,并且可以确保字符串的长度。

    区分大小写的所有字符:

     function randStr(len) { let s = ''; while (len--) s += String.fromCodePoint(Math.floor(Math.random() * (126 - 33) + 33)); return s; } // usage console.log(randStr(50)); 

    自定义字符

     function randStr(len, chars='abc123') { let s = ''; while (len--) s += chars[Math.floor(Math.random() * chars.length)]; return s; } // usage console.log(randStr(50)); console.log(randStr(50, 'abc')); console.log(randStr(50, 'aab')); // more a than b 


    #28楼

    我知道每个人都已经做好了,但是我想尽可能以最轻便的方式(轻巧的代码而不是CPU)尝试一下:

     function rand(length, current) { current = current ? current : ''; return length ? rand(--length, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz".charAt(Math.floor(Math.random() * 60)) + current) : current; } console.log(rand(5)); 

    花费很多时间,但我认为它确实显示了javascript的语法多么出色。


    #29楼

     let r = Math.random().toString(36).substring(7); console.log("random", r); 

    注意:以上算法具有以下缺点:

    • 由于在对浮点进行字符串化时会删除尾随零,因此它将生成0到6个字符之间的任意位置。
    • 这在很大程度上取决于用于对浮点数进行字符串化的算法,该算法极其复杂。 (请参阅论文“如何准确打印浮点数” 。)
    • Math.random()可能会产生可预测的(“看起来很随机”,但不是真正随机的)输出。 当您需要保证唯一性或不可预测性时,结果字符串不适合。
    • 即使它产生了6个均匀随机且不可预测的字符,由于生日悖论 ,您也可以期望仅生成大约50,000个字符串后看到重复项。 (sqrt(36 ^ 6)= 46656)

    #30楼

    您可以遍历一系列项并将其递归地添加到字符串变量中,例如,如果您需要随机DNA序列:

     function randomDNA(len) { len = len || 100 var nuc = new Array("A", "T", "C", "G") var i = 0 var n = 0 s = '' while (i <= len - 1) { n = Math.floor(Math.random() * 4) s += nuc[n] i++ } return s } console.log(randomDNA(5)); 

    展开全文
  • 文章目录字符函数和字符串函数字符串基础知识函数介绍长度受限制的字符串函数strlen(字符串长度)strcpy(字符串拷贝)strcat(连接字符串)strcmp(字符串比较)长度受限制的字符串函数介绍strncpystrncatstrncmp字符...

    字符函数和字符串函数


    我们这篇文章重点介绍以下内容:

    • 求字符串长度

      • strlen
    • 长度不受限制的字符串函数

      • strcpy
      • strcat
      • strcmp
    • 长度受限制的字符串函数介绍

      • strncpy
      • strncat
      • strncmp
    • 字符串查找

      • strstr
      • strtok
    • 错误信息报告

      • strerror
      • perror
    • 字符操作

    • 内存操作函数

      • memcpy
      • memmove
      • memset
      • memcmp

    字符串基础知识

    • 字符串就是一串或多个字符,并且以一个位模式全0的NULL字节结尾。因此我们字符串所包含的字符内部不能出现NULL字节,这个限制很少会引起问题,因为NULL字节并不存在与它相关联的可打印字符,这也是它被选为终止符的原因。NULL字节是字符串的终止符,但它本身并不是字符串的一部分,所以字符串的长度并不句括NULL字节,头文件 string.h 包含了使用字符串函数所需的原型和声明。尽管并非必需,但在程序中包含这个头文件确实是个好主意,因为有了它所包含的原型,编译器可以更好地为你的程序执行错误检查。

    函数介绍


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

    strlen(字符串长度)

    • 库函数strlen函数的原型如下:
    size_t strlen(const char*string);
    

    image-20210606192935816

    • 字符串结束标志为’\0’,strlen函数返回的是在结束标志之前的字符个数。
    #include<stdio.h>
    #include<string.h>
    int main()
    {
        char arr[]={"hello"};
        int len = strlen(arr);
        printf("%d\n",len);
        return 0;
    }
    

    image-20210606154349546

    char arr[]={"hello"};
    

    解释:

    我们在这样初始化时,后面是有\0的。strlen返回的是字符串个数,所以是5

    • 参数指向的字符串必须以’\0’结束,不然得出的结果是随机值。
    #include<stdio.h>
    #include<string.h>
    int main()
    {
        char arr[]={'h','e','l','l','o'};
        int len = strlen(arr);
        printf("%d\n",len);
        return 0;
    }
    

    image-20210606154641540

    char arr[]={'h','e','l','l','o'};
    

    解释:

    我们这样初始化时,arr数组里没有\0,strlen找不到结束标志,所以他一直往后数,所以打印到的是随机值

    • 注意函数的返回值为size_t,size_t是无符号整形。
    #include<stdio.h>
    #include<string.h>
    int main()
    {
        if(strlen("abcdef")-strlen("abcdefgh")>0)
        {
            printf(">\0");
        }
        else
        {
            printf("<");
        }
        return 0;
    }
    

    image-20210606155128810

    解释:

    我们发现这条语句和我们预想的不一样,为什么是大于呢?原因是strlen的结果是无符号数,无符号数-无符号数得到的还是无符号数,无符号数不可能是负的,所以它的结果永远都是大于0。

    strlen函数的模拟实现
    • 计数器法
    #include<stdio.h>
    int my_strlen(const char*str)
    {
        assert(str);
        int count=0;
        while(*str++)
        {
            count++;
        }
        return count;
    }
    int main()
    {
        char arr[]={"hello"};
        int len = strlen(arr);
        printf("%d\n",len);
        return 0;
    }
    

    解释:

    函数循环体判断部分*str++,*str为’\0’时退出循环,因为’\0’的ascii码值为0,为假。str指向字符串第一个字符,我们的想法是当它指向内容不为\0时,计数器++,最后返回count,我们看这个代码:*str++,++操作符的优先级比*高,我们先使用后置++,后置++是先使用后++。然后我们判断是不是\0,不是\0进入循环,计数器加加,要是\0,则退出循环,返回count。

    • 递归实现
    #include<stdio.h>
    int my_strlen(const char*str)
    {
        assert(str);
        if(*str!='\0')
        {
            return 1+my_strlen(str+1);
        }
        else
        {
            return 0;
        }
    }
    int main()
    {
        char arr[]={"hello"};
        int len = strlen(arr);
        printf("%d\n",len);
        return 0;
    }
    

    递归如何实现呢?我们利用大事化小的思想,看下图:image-20210606162923630代码解释:

    刚开始进入函数,str指向’h’,‘h’不等于\0,return 1+my_strlen(str+1),递归调用,str+1指向了’e’,b不等于\0,继续递归调用,str+1指向了’l’,l不等于\0,继续递归调用,str+1指向了’l’,l不等于\0,继续递归调用,str+1指向了’o’,o不等于\0,继续递归调用,str+1指向了’\0’,不会再进行调用,此时就到了归的环节了,归的是调用它的地方,所以依次return 1,return2,return 3,return 4,return 5,最后返回给main函数中调用它的地方。

    • 指针-指针
    #include<stdio.h>
    int my_strlen(char* str)
    {
        char* start=str;
        while(*str!='\0')
        {
            str++;
        }
        return str-start;
    }
    int main()
    {
        char arr[]="abcdef";
    	int len=my_strlen(arr);
        printf("%d\n",len);//6
        return 0;
    }
    

    解释:

    指针-指针,得到的是指针和指针之间的元素个数

    讲到这里,我们求字符串长度函数就到这里了,下面我们看这个代码:

    int main()
    {
        char arr[20]={0};
        arr="hello";///error
        return 0;
    }
    

    这个代码是错误的,arr是数组名,数组名是首元素地址,把hello放入这个(编号)地址上去吗?不行的,我们是要放进arr的内存空间的,那么我们怎么放进去呢?

    此时我们就要用到strcpy

    strcpy(字符串拷贝)

    • 库函数strcpy函数的原型如下:
    char *strcpy(char *destination,const char *source);
    

    image-20210606193209887

    我们首先来看一下它的使用:

    #include<stdio.h>
    #include<string.h>
    int main()
    {
        char arr[20]={0};
        strcpy(arr,"hello");//传字符串实际上是将hello字符串的首字符地址h传过去了
        printf("%s\n",arr);
        return 0;
    }
    

    image-20210606170320427

    • 源字符串必须以 ‘\0’ 结束。
    int main()
    {
        char arr1[20]={0};
        char arr2[]={'a','b','c'};
        strcpy(arr,arr2);
        return 0;
    }
    

    arr2能拷贝进arr1吗?

    答案是不行的,我们拷贝是要将’\0’也拷贝进去的,源字符串没有’\0’,我们不知道啥时候拷贝停止

    • 会将源字符串中的 ‘\0’ 拷贝到目标空间。
    #include<stdio.h>
    #include<string.h>
    int main()
    {
        char arr[20]={0};
        strcpy(arr,"hello");//传字符串实际上是将hello字符串的首字符地址h传过去了
        printf("%s\n",arr);
        return 0;
    }
    

    如果你不相信我们可以调试:

    image-20210606171305712

    调试后发现确实要将\0也拷贝进去的

    • 目标空间足够大,能够存入源字符串长度的空间
    #include<stdio.h>
    #include<string.h>
    int main()
    {
        char arr1[5]={0};
        strcpy(arr1,"hello world");
        return 0;
    }
    

    image-20210606173112716

    目标空间不够时,他会帮你拷贝进去,但是他会出问题

    而程序运行是会报错的
    image-20210606173219454

    我们进行打印arr1是他会打印出来,但是会报错:

    image-20210606232501412

    • 目标空间必须可变。
      #include<stdio.h>
      #include<string.h>
      int main()
      {
          char* p = "************";
          char arr[]="hello";
          strcpy(p,arr);
          return 0;
      }
    

    字符指针p指向的字符串是常量字符串,是不能修改的,所以我们的目标空间必须可变

    我们调试发现,程序会崩掉:

    image-20210606173741998

    strcpy函数的模拟实现
     #include<stdio.h>
     #include<assert.h>
     char* my_strcpy(char* dest, const char* scr)
     {
         char* temp = dest;//创建一个临时变量存储dest,以便后面返回目标字符串的地址
         assert(dest && scr);
         while (*dest++ = *scr++)
         {
             ;
         }
         return temp;
     }
     int main()
     {
         char arr1[] = "**************";
         char arr2[] = "hello";
         printf("%s\n", my_strcpy(arr1, arr2));
         return 0;
     }
     
    

    解释:

    循环判断条件是*dest++ = *scr++,首先我们要知道,a=b这个表达式的值是a的值,我们将arr1中的每个字符赋值给arr2中时,其实整个表达式的值在每次循环时的值分别为’h’e’l’l’o’\0’字符的ASSIC码值,当我们把\0赋值到arr2中时,同时整个表达式的值也为0了,所以退出循环。

    • 注意:

    我们必须保证目标字符数组的空间足以容纳需要复制的字符串。若字符串比数组长,多余的字符仍被复制,它们将覆盖原先存储于数组后面的内存空间的值。strcpy无法解决这个问题,它无法判断目标字符数组的长度。

    接下来我们看strcat连接字符串

    strcat(连接字符串)

    • 库函数strcpy函数的原型如下:
    char * strcat ( char * destination, const char * source );
    

    image-20210606192705457

    • 我们首先来看看strcat函数的使用:
    #include<stdio.h>
    #include<string.h>
    int main()
    {
        char arr1[20]={"hello "};//假设我们要追加world
        char arr2[]="world";
        //strcat(arr1,"world");//字符串追加或连接
        strcat(arr1,arr2);
        printf("%s\n",arr1);
        return 0;
    }
    

    image-20210606174630815

    strcat(arr1,“world”); strcat(arr1,arr2);这两种方式都可以字符串连接。

    • 源字符串必须以 ‘\0’ 结束。
      #include<stdio.h>
      #include<string.h>
      int main()
      {
          char arr[]={'h','e','l','l','o'};
          char arr2[]="world";
          strcat(arr1,arr2);//字符串追加或连接
          printf("%s\n",arr1);
          return 0;
      }  
    

    如果不以\0结束,是不能进行连接的,为什么不能连接呢?慢慢往下看,看到strcat函数是如何实现的你就知道啦!

    image-20210606180926715

    • 目标空间必须有足够的大,能容纳下源字符串的内容。
      #include<stdio.h>
      #include<string.h>
      int main()
      {
          char arr1[8] = { "hello "};
          char arr2[] = "world";
          strcat(arr1, arr2);//字符串追加或连接
          printf("%s\n", arr1);
          return 0;
      }
    

    如果目标空间不足够大,这里是会发生访问越界的,这里只能拷贝进wo。

    image-20210606181444256

    • 目标空间必须可修改。
      #include<stdio.h>
      #include<string.h>
      int main()
      {
          char *p = "hello ";
          char arr2[] = "world";
          strcat(p, arr2);//字符串追加或连接
          return 0;
      }
    

    image-20210606181708006

    字符指针p指向的字符串是常量字符串,是不能修改的,所以我们的目标空间必须可变

    • 字符串自己给自己追加,如何?
     #include<stdio.h>
     #include<string.h>
     int main()
     {
         char arr[10]={"abcd"};
         strcat(arr,arr);
         printf("%s\n",arr);
         return 0;
     }
    

    看下图解释:

    image-20210607005200037

    我们调试发现:确实进行了死循环的连接,因为没有了连接的结束标志\0,不知道啥时候停止,直到空间满时会报错。

    image-20210607005541404

    这里将追加的结束标志修改了,程序陷入了死循环,所以strcat函数是不能自己连接自己的

    • 目的字符串和源字符串必须要有\0

    我们这里来看一下strcat是如何实现的,我们首先看下面代码测试:

    #include<stdio.h>
    #include<string.h>
    int main()
    {
        char arr1[20]={"hello \0*************"};//假设我们要追加world
        char arr2[]="world";
        strcat(arr1,arr2);//字符串追加或连接
        printf("%s\n",arr1);
        return 0;
    }  
    
    

    image-20210606182517104

    strcat函数的实现是目标字符串中的结束标志\0被源字符串的首字符覆盖,然后依次进行追加,直到追加到\0,即追加结束。

    arr1中的\0是开始追加的标志,arr2中的\0是追加结束的标志,所以目的字符串和源字符串必须要有\0

    strcat函数的模拟实现
    • 1.找到目标字符串中的\0。
    • 2.源数据追加过去,包含\0。
    #include<stdio.h>
    void my_strcat(char *dest,const char*src)
    {
        assert(dest&&src);
        //1.找到目标字符串中的\0
        while(*dest)
        {
            dest++;
        }
        //2.源字符串的追加,包含\0
        while(*dest++=*src++)
        {
            ;
        }
    }
    int main()
    {
        char arr1[20]={"hello "};//假设我们要追加world
        char arr2[]="world";
        my_strcat(arr1,arr2);//字符串追加或连接
        printf("%s\n",arr1);
        return 0;
    } 
    

    image-20210607085826442

    关于返回值:

    image-20210606095812112

    库函数里strcat返回的是目标空间的起始地址,所以我们也将目标空间的起始地址返回,my_strcat函数返回的是指针,那么我们在打印时就可以这样打印:

    printf("%s\n",my_strcat(arr1,arr2));
    

    看如下代码:

    #include<stdio.h>
    char* my_strcat(char *dest,const char*src)
    {
        char *ret=dest;//利用ret变量保存目标空间的起始地址
        assert(dest&&src);
        //1.找到目标字符串中的\0
        while(*dest)
        {
            dest++;
        }
        //2.源字符串的追加,包含\0
        while(*dest++=*src++)
        {
            ;
        }
        return ret;//返回起始地址
    }
    int main()
    {
        char arr1[20]={"hello "};//假设我们要追加world
        char arr2[]="world";
        printf("%s\n",my_strcat(arr1,arr2));
        return 0;
    } 
    

    注意:

    和前面的strcpy一样,我们必须保证目标字符数组剩余的空间足以保存整个源字符串。但这次并不是简单地把源字符串的长度和目标字符数组的长度进行比较,我们必须考虑目标数组中原先存在的字符串。

    strcmp(字符串比较)

    在讲strcmp之前我们先来看一下下面的这段代码:

    #include<stdio.h>
    int main()
    {
        char *p="qwer";
        char *q="awerty";
        if(p>q)
        {
            printf(">\n");
        }
        else
        {
            printf("<=\n");
        }
        return 0;
    }
    

    经过调试发现:

    image-20210607091802834

    p,q是字符指针,存放的是字符串首字符的地址,我们想需要的是比较两个字符串,而这里实际上比较的是p,q指针变量的地址,所以肯定不正确。

    那么下面的这种写法可行吗?答案是也不行

    if("obc">"abcdef")//这里比较的是字符o和字符a的地址
    {
        
    }
    

    请看如下动图演示调试:

    动画

    如果比较的是字符串的话,首字符o是大于首字符a的,应该进入if语句里面,但是我们调试发现这里进入了else语句里面,所以这里比较比较的是首字符o和首字符a的地址,而不是字符串,所以这种比较方式也不可行

    这就出现了我们的主角strcmp(字符串比较函数)

    • 库函数strlen函数的原型如下:
    int strcmp(const char *string1,const char *string2);
    

    image-20210607093502701

    下面代码才能正确的比较两个字符串:

    #include<stdio.h>
    #include<string.h>
    int main()
    {
        char *p="qwer";
        char *q="awerty";
    	strcmp(p,q);
        return 0;
    }
    

    知道了这个库函数,那么这个库函数的返回值是什么呢?

    image-20210607093841550

    我们发现字符串1小于字符串2时,返回的值是小于0,字符串1等于字符串2时,返回的值为等于0,字符串1大于字符串2时,返回的值为大于0。

    #include<stdio.h>
    #include<string.h>
    int main()
    {
        char *p="qwer";
        char *q="awerty";
        int ret = strcmp(p,q);
        printf("%d\n",ret);
        return 0;
    }
    

    image-20210607094343531

    !!!这里说明一下,博主的编译器是vs2019,vs编译器他将大于返回1,等于返回0,小于返回-1,像上面的代码,只要返回的值大于0就可以了,不需要纠结和我打印的值不一样

    strcmp函数的模拟实现

    实现思路如下图解释:

    image-20210607100231504

    代码实现如下:

    #include<stdio.h>
    int my_strcmp(const char *s1,const char *s2)
    {
        assert(s1&&s2);
        while(*s1==*s2)
        {
            if(*s1=='\0')
            {
                return 0;
            }
            s1++;
            s2++;
            
        }
        if(*s1>*s2)
        {
            return 1;
        }
        else
        {
            return -1;
        }
        //return *s1-*s2;//刚好符合大于返回大于0的数,小于返回小于0的数
    }
    int main()
    {
        char *p="abcdef";
        char *q="abcc";
    	int ret = my_strcmp(p,q);
        if(ret>0)
        {
            printf("p > q\n");
        }
        else if(ret<0)
        {
            printf("p < q\n");
            
        }
        else
        {
            printf("p == q\n");
        }
        return 0;
    }
    

    image-20210607100923166

    注意1:

    如果有些人没有关注到strcmp函数的返回值,它们常常会写出这样的表达式:if(strcmp(a,b)),他以为如果两个字符串相等,它的结果将会是真。但是,事实上这个结果却刚刚好相反,因为两个字符串相等时它返回的是0。

    注意2:

    标准并没有规定当两个字符串不相等时,strcmp函数返回的具体值是多少,它只是说字符串1大于字符串2会返回大于0的数,字符串1小于字符串2会返回小于0的数。


    长度受限制的字符串函数介绍

    strncpy

    • 库函数strcpy函数的原型如下:
    char *strncpy(char *dest,const char *source,size_t count);
    

    image-20210607101921079

    我们发现strncpy相比于strcpy增加了一个参数,这个参数类型为size_t,无符号的数,strncpy函数用于将指定长度的字符串复制到字符数组的前(指定长度)个字符中

    我们先来看一看它的使用:

    #include<stdio.h>
    #include<string.h>
    int main()
    {
        char arr1[20]="abcdef";
        char arr2[]="qwer";
        strncpy(arr1,arr2,2);//相对安全
        //strcpy(arr1,arr2);
        printf("%s\n",arr1);
        return 0;
    }
    

    image-20210607102017163

    strncpy函数用于将指定长度的字符串复制到字符数组中,他将arr2的前两个字符复制到arr1中。

    当传入的长度要比源字符串长能拷贝进去吗?

    #include<stdio.h>
    #include<string.h>
    int main()
    {
        char arr1[20]="abcdef";
        char arr2[]="qwer";
        strncpy(arr1,arr2,6);//相对安全
        //strcpy(arr1,arr2);
        printf("%s\n",arr1);
        return 0;
    }
    

    image-20210607102713955

    当传入的长度要比源字符串长,strncpy函数也是能拷贝进去,而且打印的arr是qwer,为什么不是qweref呢?为什么呢?

    我们首先调试一下:

    开始时的arr1:

    image-20210607113433277

    最后的arr1:

    image-20210607113532168

    我们发现他不仅将qwer拷贝过去了,他还会给你拷贝够6个,只不过后面拷贝过去的是\0。实际上如果strlen(source)的值小于指定的长度,目标数组就用额外的NULL字节填充到指定长度,strlen(source)的值大于或等于指定的长度,那么只有指定长度个字符被复制到目标字符串中。注意!此时并不会将以\0拷贝过去。

    当然我们必须需要知道库函数的代码是怎么样的,才能知道结果为什么是这样的,我们首先先来看一下库函数是怎么实现的,下面代码为strncpy在库函数中的实现:

    char * __cdecl strncpy (
            char * dest,
            const char * source,
            size_t count
            )
    {
            char *start = dest;
    
            while (count && (*dest++ = *source++) != '\0')    /* copy string */
                    count--;
    
            if (count)                              /* pad out with zeroes */
                    while (--count)
                            *dest++ = '\0';
    
            return(start);
    }
    

    当传入的长度要比源字符串长时的解释:

    count刚开始是6,每拷贝一次conut–,当字符串qwer拷贝完之后,遇到了\0,while循环终止,此时count是2,然后,count不为0,为真,进入if语句,将目标字符串拷贝进去的串后面的2个字符填充为\0,所以前面的代码打印的是qwer,这就解释了当传入的长度要比源字符串长时的情况。

    strncat

    • 库函数strlen函数的原型如下:
    char *strncat(char *dest,const char *source,size_t count);
    

    image-20210607111554433

    strncat与strcat相比于strncpy与strcpy是一样的道理,strncat相比于strcat增加了一个参数,这个参数类型为size_t,无符号的数,strncat函数用于将指定长度的字符串连接到字符数组中

    • 我们先来看一看它的使用:
    #include<stdio.h>
    #include<string.h>
    int main()
    {
        char arr1[20]="hello ";
        char arr2[]={"world"};
        strncat(arr1,arr2,3);
        printf("%s\n",arr1);
        return 0;
    }
    

    image-20210607112221779

    strncat函数用于将指定长度的字符串连接到字符数组中,这里将arr2中的前三个字符连接到arr1中去

    那么当指定字符长度要比源字符串长时能连接过去吗?

    #include<stdio.h>
    #include<string.h>
    int main()
    {
        char arr1[20]="hello ";
        char arr2[]={"world"};
        strncat(arr1,arr2,10);
        printf("%s\n",arr1);
        return 0;
    }
    

    image-20210607114617702

    答案是可以的。

    我们来看一看库函数中strncat是怎么实现的:

    char * __cdecl strncat (
            char * front,
            const char * back,
            size_t count
            )
    {
            char *start = front;
    
            while (*front++)
                    ;
            front--;
    
            while (count--)
                    if ((*front++ = *back++) == 0)
                            return(start);
    
            *front = '\0';
            return(start);
    }
    

    当我们指定字符传10进去时,我们看第13行代码,while(count–)循环里面进行连接,当连接到\0时,count不为0,循环继续,此时也将源字符串中的\0连接上去了,此时if语句里面的表达式成立(这里注意例如:a=b,则整个表达式的结果为a的值),然后return回目的字符串的地址了;当我们指定字符传3时,while循环当count变为0时,不会再进入循环,此时我们还没有将\0连接上去,所以后面*front = ‘\0’;这句代码就将\0连接上去了,最后返回目的字符串的地址。

    注意:

    和strncpy不同的是,它最多从源字符串中复制源字符串的长度个字符到目标数组的后面,strcat总是在结果字符串后面添加一个\0,它不会像strncpy那样在指定长度大于源字符串长度时对目标数组用\0进行填充。strncat最多向目标数组复制源字符串的长度个字符(加\0),它不会去管目标参数除去原先的字符串还够不够存储连接过来的字符串。

    strncmp

    • 库函数strlen函数的原型如下:
    int strncmp( const char *string1, const char *string2, size_t count );
    

    image-20210607121418471

    长度受限制的字符串函数与各自对应的长度受限制的字符串函数是十分相似的,strncmp与strcmp相比于前面两个是一样的道理,strncmp相比于strcmp增加了一个参数,这个参数类型为size_t,无符号的数,strncmp函数的作用是:字符串1的前指定长度的字符与字符串2中的前(指定长度)的字符进行比较

    返回值:image-20210607121758827

    字符串1的前指定长度的字符与字符串2中的前(指定长度)的字符相等时,返回0

    字符串1的前指定长度的字符小于字符串2中的前(指定长度)的字符时,返回<0的值

    字符串1的前指定长度的字符大于字符串2中的前(指定长度)的字符时,返回>0的值

    • 我们先来看strcmp比较以下这两个字符串的结果:
    #include<stdio.h>
    #include<string.h>
    int main()
    {
        char *p="abcdef";
        char *q="abcdqwert";
        int ret = strcmp(p,q);
        printf("%d\n",ret);//-1
        return 0;
    }
    

    image-20210607121954068

    p所指向的字符串是比q指向的字符串小的,所以返回的是小于0的数,这里我的vs编译器返回的是-1

    然后我们再来看看strncmp指定长度比较以下两个字符串的结果:

    #include<stdio.h>
    #include<string.h>
    int main()
    {
        char *p="abcdef";
        char *q="abcdqwert";
        int ret = strncmp(p,q,4);
        printf("%d\n",ret);
        return 0;
    }
    

    image-20210607122434704

    p所指向字符串的前4个字符和q指向的字符串的前4个字符是相等的,所以返回的是0


    字符串查找函数

    strstr(查找子串)

    • 作用是查找子串并返回字符串中首次出现子串的地址。
    • 库函数strlen函数的原型如下:
    char *strstr( const char *string, const char *strCharSet );
    

    image-20210607123726388

    • 函数参数以及返回值解释:

    image-20210607125948673

    • 接下来我们来看一看它的使用:
    #include<stdio.h>
    #include<string.h>
    int main()
    {
        char arr1[]="abcdefabcdef";
        char arr2[]="bcd";
        //在arr1中查找是否包含arr2
        char *ret = strstr(arr1,arr2);//找到了就返回第一次出现的地址
        if(ret==NULL)
        {
            printf("没找到\n");
        }
        else
        {
            printf("找到了:%s\n",ret);
        }
        return 0;
    }
    

    image-20210607130534256

    我们可以看到它返回值存入ret变量中,我们将ret打印,它确实返回的是子串第一次出现的地址

    同样的我们可以测试找不到的情况:

    image-20210607130935505

    可以看到此时ret为NULL,说明返回的是NULL,打印了没找到

    strstr函数的模拟实现
    #include<stdio.h>
    char *my_strstr(const char *str1,const char *str2)
    {
        assert(str1&&str2);
        const char *s1=NULL;
        const char *s2=NULL;//我们比较时不使用str1和str2,我们创建两个临时变量来移动比较,因为如果我们用str1和str2一直进行移动比较,到最后我们不知道起始的串在哪里,无法找到了。
        const char *cp=str1;//指向每次开始比较时起始的第一个字符
        if(*str2=='\0')//需要查找的字符串为空字符串的话,返回要浏览的字符串
        {
            return (char*)str1;//因为函数返回值为char*,所以需要强制类型转换
        }
        while(*cp)//cp指向不为\0时进入
        {
            s1=cp;//每次将新的cp赋给s1
            s2=str2;//将需要查找的串赋给s2
            while(*s1 && *s2 && (*s1==*s2))//s1和s2指向内容相等时s1、s2分别加加指向下一个字符进行比较,直到s1指向内容不等于s2所指向内容时跳出循环,并且s1和s2指向\0时,循环也停止
            {
                s1++;
                s2++;
            }
            if(*s2=='\0')//当s2指向为\0时,说明比较结束,是子串,返回子串的起始位置
            {
                return (char*)cp;
            }
            cp++;//s2不是\0时,说明不是子串,cp指向下一个字符开始比较
        }
        return NULL;
    }
    int main()
    {
        char arr1[]="abcdefabcdef";
        char arr2[]="bcd";
        //在arr1中查找是否包含arr2
        char *ret = my_strstr();//找到了就返回第一次出现的地址
        if(ret==NULL)
        {
            printf("没找到\n");
        }
        else
        {
            printf("找到了:%s\n",ret);
        }
        return 0;
    }
    

    strtok(分割字符串)

    • 库函数strtok函数的原型如下:
    char *strtok( char *strToken, const char *strDelimit );
    
    • strtok()用来将字符串分割成一个个片段。参数strToken指向欲分割的字符串,参数strDelimit 则为分割字符的字符串,当strtok()在参数strToken 的字符串中发现到参数strDelimit 的分割字符时则会将该字符改为\0 字符。在第一次调用时,strtok()必需给予参数strToken字符串,往后的调用则将参数strToken设置成NULL。每次调用成功则返回下一个分割后的字符串指针。

    image-20210607153520189

    • 函数参数以及返回值解释:

    image-20210607160512949

    注意:

    如果strtok函数的第1个参数不是NULL,函数将找到字符串的第1个标记。strtok同时将保存它在字符串中的位置。如果strtok 函数的第1个参数是NULL,函数就在同一个字符串中从这个被保存的位置开始像前面一样查找下一个标记。如果字符串内不存在更多的标记,strtok函数就返回一个NULL 指针。在典型情况下,在第1次调用strtok时,向它传递一个指向字符串的指针。然后,这个函数被重复调用(第1个参数为NULL),直到它返回NULL为止。

    • 接下来我们来看一看strtok函数的使用:
    #include<stdio.h>
    #include<string.h>
    int main()
    {
    	char arr[] = "www.baidu.com";
    	//printf("%s\n",strtok(arr, "."));
    	//printf("%s\n", strtok(NULL, "."));
    	//printf("%s\n", strtok(NULL, "."));*/
    	char* temp;
    	for (temp = strtok(arr, "."); temp != NULL; temp = strtok(NULL, "."))
    	{
    		printf("%s\n", temp);
    	}
    	return 0;
    }
    

    image-20210607172907985

    我们调试可以看到每一步strtok的调用的返回值temp的变化:

    strtok


    错误信息报告

    strerror

    • 库函数strerror函数的原型如下:

      char *strerror( int errnum );
      

    image-20210607173152608

    我们在使用库函数的时候,调用库函数失败时,都会设置错误码,或者说当你调用一些函数,请求操作系统执行一些功能如打开文件时,如果出现错误,操作系统是通过设置一个外部的整型变量errno进行错误代码报告的。strerror函数把其中一个错误代码作为参数并返回一个指向用于描述错误的字符串的指针。

    我们的strerror函数翻译错误码并返回一个指向用于描述对应错误信息的字符串的指针。

    • 函数参数以及返回值解释:

    image-20210607174810756

    • 接下来我们来看一看strerror函数的使用:
    #include<stdio.h>
    #include<string.h>
    int main()
    {
        printf("%s\n",sterror(0));
        printf("%s\n",sterror(1));
        printf("%s\n",sterror(2));
        printf("%s\n",sterror(3));
        printf("%s\n",sterror(4));
        printf("%s\n",sterror(5));
        FILE* pf=fopen("test.txt","r");
        if(pf==NULL)//打开文件失败时,会返回NULL
        {
            printf("%s\n",strerror(errno));
            return 1;
        }
        fclose(pf);
        pf=NULL;
        return 0;
    }
    

    image-20210607174032976

    错误码0,1,2,3,4,5所对应的错误信息如上图打印,当我们打开一个文件失败时,操作系统是通过设置一个外部的整型变量errno进行错误代码报告的。

    perror(打印错误信息)

    perror函数的功能是打印错误信息,它和strerror函数不同的是,strerror返回的是字符指针,perror返回的是void,使用strerror函数时,如果需要打印错误信息,需要我们自己写printf进行打印,而perror函数在调用它时,就将错误信息打印了。参数 string 所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量errno的值来决定要输出的字符串。

    • 库函数strerror函数的原型如下:
     void perror( const char *string );
    

    image-20210609222706679

    perror函数实现主要是以下两步:

    • 1、首先把错误码转换为错误信息

    • 2、打印错误信息(包含了自定义信息)

    #include<stdio.h>
    #include<string.h>
    int main()
    {
    	printf("%s\n", strerror(0));
    	printf("%s\n", strerror(1));
    	printf("%s\n", strerror(2));
    	printf("%s\n", strerror(3));
    	printf("%s\n", strerror(4));
    	printf("%s\n", strerror(5));
    	FILE* pf = fopen("test.txt", "r");
    	if (pf == NULL)//打开文件失败时,会返回NULL
    	{
         	//printf("%s\n",strerror(errno));
    		perror("fopen");
    		return 1;
    	}
    	fclose(pf);
    	pf = NULL;
    	return 0;
    }
    

    它会直接打印出错误信息:

    image-20210609091909500

    我们前面讲到的都是操作字符串的,下面我们来讲一些操作字符的函数。


    字符操作

    • 标准库包含了两组函数,用于操作单独的字符,它们的原型位于头文件ctype.h。第一组函数为字符分类,第二组函数用于转换字符。下面我们来看这两组函数。

    字符分类

    每一个分类函数接收一个包含字符值得整形参数。函数测试这个字符并返回一个整形值,表示真或假。

    分类函数如下表:

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

    我们来看两个字符分类函数的测试:

    #include<ctype.h>
    #include<stdio.h>
    int main()
    {
        char ch='#';
        int ret = isdigit(ch);//是不是数字字符
        printf("%d\n",ret);
        return 0;
    }
    

    image-20210609182023305

    image-20210609182652047

    如果是数字字符返回非零,如果不是数字字符返回零

    #include<ctype.h>
    #include<stdio.h>
    int main()
    {
        char ch='a';
        int ret = islower(ch);//是不是小写字母
        printf("%d\n",ret);
        return 0;
    }
    

    image-20210609182530309

    image-20210609182559009

    如果是小写字母返回非零,如果不是小写字母返回零

    字符转换

    • 转换函数把大写字母转换成小写字母或者把小写字母转换为大写字母。
    int tolower(int ch);
    int toupper(int ch);
    
    • toupper函数返回其参数的对应大写形式,tolower函数返回其参数的对应小写形式。如果函数的参数并不是一个处于适当大小写状态的字符(即toupper的参数不是小写字母或tolower的参数不是大写字母),函数将不修改参数直接返回。
    #include<stdio.h>
    #include<ctype.h>
    int main()
    {
        char arr[20]={0};
        scanf("%s",arr);
        int i=0;
        while(arr[i]!='\0')
        {
            if(isupper(arr[i]))
            {
                arr[i]=tolower(arr[i]);
            }
            printf("%c",arr[i]);
            i++;
        }
        return 0;
    }
    

    image-20210609183039734

    如果函数的参数并不是一个处于适当大小写状态的字符(即toupper的参数不是小写字母或tolower的参数不是大写字母),函数将不修改参数直接返回。

    image-20210609184323972

    我们在判断一个ch字符是不是一个大写字符时可以这样判断:

    if(ch>='A'&&ch<='Z')
    
    • 这样判断是可以判断,但是程序的可移植性不好,可能在其他使用不同的字符集机器上可能是不会成功的,而下面这个语句,无论在哪个机器上,都能够顺利运行的。
    if(isupper(ch));
    

    内存操作函数

    • 我们上面讲的函数都是操作字符串或者字符的,字符串是以NULL字节结尾的,所以字符串内部是不能包含任何NULL字节的,那么对于那些内部包含零值的数据类型该怎么处理呢?我们是无法通过字符串函数来操作这类数据类型的,这时就出现了另外一组相关的函数—内存操作函数

    内存操作函数的操作和字符串函数类似,只是它可以处理任意的字节序列,接下来我们首先来看memcpy函数。

    memcpy

    • 库函数memcpy函数的原型如下:
    void *memcpy( void *dest, const void *src, size_t count );
    

    image-20210609111731601

    • 函数的参数和返回类型解释:

    image-20210609192400038

    • 下面我们来看一下它的使用:
    #include<string.h>
    int main()
    {
        int arr1[10]={1,2,3,4,5,6,7,8,9,10};
    	int arr2[20]={0};
        memcpy(arr2,arr1,20);
        return 0;
    }
    

    image-20210609192956909

    • 我们调试能够看到memcpy函数将arr1的前20个字节拷贝到了arr2中
    memcpy函数的模拟实现

    memcpy函数是怎么将源数据拷贝到目的地的呢?我们这样进行拷贝可以吗?

    void *my_memcpy(void *dest,const void *src,size_t num)
    {
        assert(dest&&src);
        void *ret=dest;
        while(num--)
        {
            *dest++ = *src++;
        }
        return ret;
    }
    

    这样显然是不可以的,因为我们的函数参数是void*的类型,我们是不能对void*类型的参数进行解引用操作的,而且我们解引用也不能明确的知道他会访问几个字节,所以我们要先将dest和src强制类型转换为char*,我们一个字节一个字节的进行拷贝。那么拷贝成功一个字节我们怎么让dest和src指向下一个字节呢?同样的我们需要将dest和src分别强制类型转换为char*然后+1,这样就向后移动一个字节了

    所以我们的代码应该这样写:

    #include<assert.h>
    
    void *my_memcpy(void *dest,const void *src,size_t num)
    {
        assert(dest&&src);
        void *ret=dest;//保存拷贝目的地的起始地址
        while(num--)
        {
            *(char*)dest = *(char*)src;//一个字节一个字节拷贝
            dest = (char*)dest+1;
            src = (char*)src+1;
        }
        return ret;//返回拷贝目的地的起始地址
    }
    int main()
    {
        int arr1[10]={1,2,3,4,5,6,7,8,9,10};
    	int arr2[20]={0};
        my_memcpy(arr2,arr1,20);
        return 0;
    }
    

    完成了memcpy函数的模拟,我们思考这么一个问题:我们将arr1数组的1,2,3,4,5拷贝到3,4,5,6,7所在的内存空间上,可不可以呢?

    image-20210609200050886

    my_memcpy(arr1+2,arr1,20);
    

    image-20210609200730081

    我们结果和我们想的并不一样,我们想的是1,2,1,2,3,4,5,8,9,10,但是结果却是1,2,1,2,1,2,1,8,9,10,为什么呢?看下图解释:

    image-20210609203746988

    所以memcpy应该拷贝不重叠的内存

    这时就有了memmove函数,memmove可以处理内存重叠的情况。

    memmove

    • 库函数memmove函数的原型如下:
    void *memmove( void *dest, const void *src, size_t count );
    

    image-20210609151259594

    memmove的参数和返回类型的解释与memcpy函数是一样的,这里就不再进行解释了。

    • 下面我们来看看memmove函数的使用:
    #include<string.h>
    int main()
    {
        int arr1[10]={1,2,3,4,5,6,7,8,9,10};
        memmove(arr1+2,arr1,20);
        return 0;
    }
    

    image-20210609204605811

    我们调试发现,已经进行了拷贝,memmove可以处理内存重叠的情况。

    memmove函数的模拟实现

    memmove函数该怎么实现呢?请看下图说明:

    image-20210609212838469

    我们进行拷贝时,可以从前往后拷贝,也可以从后往前拷贝。当我们想要把如图中的1 2 3 4 5拷贝到3 4 5 6 7上去,从前往后显然是不行的,但是我们仔细思考发现从后往前是可以拷贝成功的,数组里面元素地址从前往后是从低到高的。我们得出结论:当src的地址大于dest的地址时,我们可以用从前往后进行拷贝,当src的地址小于dest的地址时,我们可以用从后往前进行拷贝,当dest大于src加指定字节数时,无论是从前往后还是从后往前都可以完成拷贝。

    我们也可以将上面三种情况分为两种情况:dest<src的情况和dest>=的情况,因为第三种情况既可以从前往后拷贝,又可以从后往前拷贝。

    最终的memmove的模拟实现代码如下:

    void *my_memmove(void *my_memmove(void *dest,const void *src,size_t num))
    {
        assert(dest&&src);
       	void* ret=dest;
        if(dest<src)
        {
            //前->后
            while(num--)
            {
                *(char*)dest=*(char*)src
            	dest=(char*)dest+1;
                src=(char*)src+1;
            }
        }
        else
        {
            //后->前
            while(num--)
            {
                *((char*)dest+num)=*((char*)src+num);
            }
        }
        return ret;
    }
    

    memmove函数我们的讲解就到这里啦!

    下面我们来看一个小问题:

    我们在前面中使用自己模拟实现的memcpy函数时,发现是不能拷贝重叠内存的,可能有人会疑惑,为什么我的memcpy库函数是可以实现拷贝重叠内存的呢?是不是我们的模拟实现的memcpy函数错了呢?

    image-20210609215141086

    如上图,我们的vs编译器中的memcpy函数就可以重叠拷贝。

    解释:

    我们实现的memcpy当然不是错误的,对于memcpy函数,标准规定只要实现了不重叠拷贝时的情况就可以了,而vs编译器中的实现既可以拷贝不重叠内存,也可以拷贝重叠内存,在vs编译器中,memcpy是可以实现重叠拷贝的,但是在其他编译器环境中,memcpy不一定也能实现重叠拷贝。

    memcmp(内存比较)

    函数的作用是比较内存区域buf1和buf2的前count个字节

    库函数memcmp函数的原型如下:

    int memcmp( const void *buf1, const void *buf2, size_t count );
    

    image-20210609155837508

    • 下面我们来看一下它的使用:
    #include<stdio.h>
    #include<string.h>
    int main()
    {
        float arr1[]={1.0,2.0,3.0,4.0};
        float arr2[]={1.0,3.0};
        int ret=memcmp(arr1,arr2,4);//0
        //memcmp - strcmp返回值的设置是一样的
        return 0;
    }
    

    image-20210609220345738

    关于函数返回值:

    image-20210609160153875

    memcmp函数的返回值和strcmp函数返回值的设置是一样的,这里就不进行描述了,前面我们已经详细说过了。

    memset

    库函数memset函数的原型如下:

    void *memset( void *dest, int c, size_t count );
    

    image-20210609220953895

    将指针变量 dest所指向的前 count 字节的内存单元用一个“整数” c 替换,注意 c 是 int 型。dest 是 void 型的指针变量,所以它可以为任何类型的数据进行初始化。*

    • 下面我们来看一下它的使用:
    #include<stdio.h>
    #include<string.h>
    int main()
    {
        int arr[10]={0};
        memset(arr,1,20);//以字节为单位设置内存的
        return 0;
    }
    

    image-20210609221805429

    将arr中的前20个字节初始化为1


    总结

    • 字符串是零个或多个字符的序列,该序列以\0为结尾。标准库提供了一些函数处理这些字符串,它们的原型位于头文件string.h中。

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

    • strlen函数用于计算一个字符串的长度,要注意它的返回值是一个无符号的整数,我们将它用于表达式中时要小心。strcpy函数把一个字符串从一个位置复制到另一个位置,而strcat函数把一个字符串的一份拷贝连接到另一个字符串的后面,我们要注意,这两个函数都是假定它们的参数是有效的字符串,如果源字符串和目标字符串出现重叠时,函数的结果是未定义的。strcmp函数对两个字符串进行比较,它的返回值提示第一个字符串是大于、等于、或者小于第二个字符串。

    长度受限制的字符串函数

    • strncpy、strncat、strncmp都类于它们所对应的长度不受限制的函数,区别在于它们多了一个长度参数,在strncpy函数中,长度指定了多少个字符将被拷贝进目标字符数组中,如果源字符串比指定字符长,将不会拷贝\0;如果源字符串比指定长度短,它会将源字符串全部拷贝进目标字符数组中,然后剩余的长度将以\0填充进目标字符数组。在strncat函数中,长度指定了多少个字符将被连接到目标字符数组后面,当指定长度大于源字符串长度时,它会拷贝整个源字符串过去(加\0拷贝过去),当指定长度小于源字符串长度时,它会将指定长度字符拷贝到目标字符数组中(加\0拷贝过去),所以不管指定长度和源字符串长度的关系大小,它都会将\0拷贝过去。在strncmp函数中,长度指定了字符比较的数目,它的返回值提示第一个字符串的前指定字符是大于、等于、或者小于第二个字符串的前指定字符。

    字符串查找函数

    • 字符串查找函数我们这里讲解了两个函数,strstr函数和strtok函数,strstr函数在一个字符串中查找另一个字符串第一次出现的位置,strtok函数把一个字符串分割成几个标记,每次当它被调用时,都返回一个指向字符串中下一个标记位置的指针,当它找不到标记时,会返回空指针;这些标记由一个指定字符集的一个或多个字符分隔。

    错误信息报告函数

    • strerror把一个错误代码作为它的参数,它返回一个指向描述错误信息字符串的指针。perror函数和strerror函数不同的是,我们在调用perror时,它会打印我们传入的信息,然后后面跟着错误信息,而strerror函数则需要我们自己打印错误信息。

    字符操作函数

    • 标准库还提供了各种用于测试字符和转换字符的函数,我们使用这些函数的程序比那些自己执行字符测试和字符转换的程序更具移植性。toupper函数把一个小写字母字符转换成大写字母字符,而tolower函数则把一个大写字母转换成小写字符。测试字符大家可以认真看字符分类那一块讲解的那一个表格。它们的原型位于头文件ctype.h中

    内存操作函数

    • memxxx类的函数提供了类型字符串函数的功能,但它们可以处理包括空字节在内的任意字节。这些函数都接受一个长度参数,memcpy将源参数向目标参数复制有长度参数指定的字节数,memmove与memcpy功能相同,只是它可以处理源参数和目标参数内存重叠的情况,memcmp函数比较两个参数的指定字节,memset函数把一个序列的指定字节初始化为特定的值。

    有关字符串和字符函数的讲解就到这里,由于博主水平有限,如有错误之处,还望指正,欢迎大家学习交流!

    大家如果觉得文章不错的话别忘了点赞评论加收藏哦

    展开全文
  • Python与字符串(二)

    2020-07-31 10:34:32
    Python入门与字符串字符串(一)字符串的表示方法(二)字符串的使用方法(大小写,长度,拼接)字符串的长度字符串的大小写(三)访问字符串的值 字符串 仅考虑零基础人员学习。 在讲解字符串前,先回顾数字数据...
  • 字符串编码

    2015-09-06 09:46:13
    内容包括:Unicode 本身的概述、最佳实践、字符串本地化、字符串解析以及一些字符串绘画。这些主题对每一个 iOS 和 OS X 开发者来说都息息相关。希望我们的文章能让你的日常工作变得更加容易。
  • Python 1-10 字符串操作

    千次阅读 2021-03-26 11:14:28
    Python 字符串练习
  • 字符串类编程题

    2020-07-22 21:35:43
    实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。 思路: 首先要确定是不是在本身上修改,如果是,则要题目保证内存足够。否则,...
  • //true   创建s1对象时,就在常量池中产生了一个"abc"字符串,所以再用赋值的方式创建相同内容的字符串时,就直接使用常量池中,不再重复创建,所以两种比较方式的结果都是true,因为本就是一个对象。 1.4.3 常规...
  • PHP 生成不重复标识符的方法

    千次阅读 2018-03-21 15:55:35
    避免文件名称的重复 二. 常规方案 2.1 guid 32 字符十六进制数。 格式:GUID 的格式为“xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx”,其中每个 x 是 0-9 或 a-f 范围内的一个32十六进制数。例如:6F9619FF-8B...
  • 一步步教你优化Delphi字符串查找

    千次阅读 2009-01-15 13:09:00
    一步步教你优化Delphi字符串查找 2008-12-15 20:20:33 作者:sirius 来源:互联网 浏览次数:23 文字大小:【大】【中】【小】简介: 本人在编写离线浏览器WebSeizer的过程中,用到大量字符串处理函数,这是很...
  • 列表、元组和字符串共同属性: 属于有序序列,其中的元素有严格的先后顺序 都支持双向索引,索引范围 [ -L, L-1 ] ,L – 表示列表、元组和字符串的长度(分正向索引和反向索引)。 正向索引:0 表示第 1 个元素,...
  • 指针 与 数组 ( 指针 | 数组 | 指针运算 | 数组访问方式 | 字符串 | 指针数组 | 数组指针 | 多维数组 | 多维指针 | 数组参数 | 函数指针 | 复杂指针解读)
  • sql server 自定义函数

    2013-06-02 13:14:00
    SQL SERVER中是允许有自定义函数,Microsoft SQL Server 并将用户限制在定义为 Transact-SQL 语言一部分的内置函数上,而是允许用户创建自己的用户定义函数。 函数是由一个或多个 Transact-SQL 语句组成的子...
  • //strnatcasecmp():自然方式区分大小写比较字符串 echo ' '; $str1='a10A'; $str1='a2a'; echo strnatcmp($str1,$str2); echo strnatcasecmp($str1,$str2); //字符串模糊比较 //similar_text():...
  • 来自:... ... 02、去除字符串中连续的分割符 03、求第一个字符串中第二个串的个数 04、综合模糊查询 05、将进制转成十六进制 ...06、求两个字符串中相同的汉字及字母的个数 ...07、生成n位随机字符串 08、
  • 自定义主键生成策略

    千次阅读 2012-07-20 12:33:35
    写一篇关于数据库主键生成的文章,大家共同研究一下。 首先,确定应用场景。多个程序访问同一个数据库,每个程序生成的主键是不会重复的,那么数据也会相对独立。...生成规则为了不重复一般都是两种思路,一种是自
  • strrchr — 查找指定字符在字符串中的最后一次出现 string strrchr( string $haystack ,  mixed  $needle ) 正则表达式语法:  1)  定界符  / /   2)  原子:  \s  是正则表达式匹配的...
  • 相对于字典树来说,后缀树并不是针对大量字符串的,而是针对一个或几个字符串来解决问题。比如字符串的回文子串,两个字符串的最长公共子串等等。 后缀数组:就是把某个字符串的所有后缀按照字典序排序后的数组。...
  • if($len>10 ) {//位数过长重复字符串一定次数 $chars= $type==1? str_repeat($chars,$len) : str_repeat($chars,5); } if($type!=4) { $chars = str_shuffle($chars); $str = substr($chars,0,$len); }else{ ...
  • 高手的自定义函数库

    2012-07-05 10:01:13
    Maco wang 的自定义函数库 http://blog.csdn.net/maco_wang/article/details/6261639
  • int转字符串字符串转int?strcat,strcpy,strncpy,memset,memcpy的内部实现? c++11标准增加了全局函数std::to_string 可以使用std::stoi/stol/stoll等等函数 strcpy拥有返回值,有时候函数原本需要返回值,但为了...
  • SQL SERVER中是允许有自定义函数,Microsoft SQL Server 并将用户限制在定义为 Transact-SQL 语言一部分的内置函数上,而是允许用户创建自己的用户定义函数。 函数是由一个或多个 Transact-SQL 语句组成的子...
  • SQL SERVER中是允许有自定义函数,Microsoft SQL Server 并将用户限制在定义为 Transact-SQL 语言一部分的内置函数上,而是允许用户创建自己的用户定义函数。 函数是由一个或多个 Transact-SQL 语句组成的子程序...
  • mysql中自定义函数编程

    千次阅读 2015-08-07 10:02:19
    函数名,应该合法的标识符,并且应该与已有的关键字冲突。 一个函数应该属于某个数据库,可以使用db_name.funciton_name的形式执行当前函数所属数据库,否则为当前数据库。 参数部分,由参数名和参数类型...
  • SQL SERVER中是允许有自定义函数,Microsoft SQL Server并将用户限制在定义为Transact-SQL语言一部分的内置函数上,而是允许用户创建自己的用户定义函数。函数是由一个或多个Transact-SQL语句组成的子程序,可用于...
  • SQL SERVER中是允许有自定义函数,Microsoft SQL Server 并将用户限制在定义为 Transact-SQL 语言一部分的内置函数上,而是允许用户创建自己的用户定义函数。 函数是由一个或多个 Transact-SQL 语句组成的子...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 13,998
精华内容 5,599
关键字:

自定义十位不重复随机字符串