C语言10 多维数组

多维数组的定义

  1. 比如一个班有5个组,每个组有9个人

    int arr[45] 或者 intarr[5*9] 或者 int arr[5][9]
    
  2. 比如一个县有5个学校,每个学校有3个年级,每个年级有4个班,每个班有5个组,每个组有9个人

    int arr[5*3*4*5*9] 或者int arr[5][3][4][5][9]
    int arr[5][3][4][5][9] ,又成为多维数组.

多维数组的初始化

int arr[3][4] = {
    {1,2,3,4},
    {5,6,7,8},
    {9,7,6,5}
};

多维数组的存储方式

int arr[3][4];

反汇编对比

8:        int arr[3*4] = {1,2,3,4,5,6,7,8,9,10,11,12};
00401028 C7 45 D0 01 00 00 00 mov         dword ptr [ebp-30h],1
0040102F C7 45 D4 02 00 00 00 mov         dword ptr [ebp-2Ch],2
00401036 C7 45 D8 03 00 00 00 mov         dword ptr [ebp-28h],3
0040103D C7 45 DC 04 00 00 00 mov         dword ptr [ebp-24h],4
00401044 C7 45 E0 05 00 00 00 mov         dword ptr [ebp-20h],5
0040104B C7 45 E4 06 00 00 00 mov         dword ptr [ebp-1Ch],6
00401052 C7 45 E8 07 00 00 00 mov         dword ptr [ebp-18h],7
00401059 C7 45 EC 08 00 00 00 mov         dword ptr [ebp-14h],8
00401060 C7 45 F0 09 00 00 00 mov         dword ptr [ebp-10h],9
00401067 C7 45 F4 0A 00 00 00 mov         dword ptr [ebp-0Ch],0Ah
0040106E C7 45 F8 0B 00 00 00 mov         dword ptr [ebp-8],0Bh
00401075 C7 45 FC 0C 00 00 00 mov         dword ptr [ebp-4],0Ch
10:       int arr[3][4] = {
11:           {1,2,3,4},
00401028 C7 45 D0 01 00 00 00 mov         dword ptr [ebp-30h],1
0040102F C7 45 D4 02 00 00 00 mov         dword ptr [ebp-2Ch],2
00401036 C7 45 D8 03 00 00 00 mov         dword ptr [ebp-28h],3
0040103D C7 45 DC 04 00 00 00 mov         dword ptr [ebp-24h],4
12:           {5,6,7,8},
00401044 C7 45 E0 05 00 00 00 mov         dword ptr [ebp-20h],5
0040104B C7 45 E4 06 00 00 00 mov         dword ptr [ebp-1Ch],6
00401052 C7 45 E8 07 00 00 00 mov         dword ptr [ebp-18h],7
00401059 C7 45 EC 08 00 00 00 mov         dword ptr [ebp-14h],8
13:           {9,10,11,12}
00401060 C7 45 F0 09 00 00 00 mov         dword ptr [ebp-10h],9
00401067 C7 45 F4 0A 00 00 00 mov         dword ptr [ebp-0Ch],0Ah
0040106E C7 45 F8 0B 00 00 00 mov         dword ptr [ebp-8],0Bh
00401075 C7 45 FC 0C 00 00 00 mov         dword ptr [ebp-4],0Ch
14:       };

通过观察我们发现,所谓多维数组和一维数组,在内存分配上完全相同

所以

int arr[3*4]  完全等价于  int arr[3][4]

使用多维数组的原因是,使用方便 寻找元素非常方便,在底层方面和一维数组没有任何的区别

多维数组的读写

比如:一年有12个月,每个月都有一个平均气温,存储5年的数据

int arr[5][12] = {
    {1,2,1,4,5,6,7,8,9,1,2,3}, //0
    {1,2,1,4,5,6,7,8,9,1,2,3}, //1
    {1,2,1,4,5,6,7,8,9,1,2,3}, //2
    {1,2,1,4,5,6,7,8,9,1,2,3}, //3
    {1,2,1,4,5,6,7,8,9,1,2,3}  //4
};

===
获取第一年第9个月的数据:
arr[0][8]
编译器是如何找到这个数据的:
arr[0*12+8]

===
获取第二年第8个月的数据:
arr[1][7]
编译器是如何找到这个数据的
arr[1*12+7]

多维数组的存储于读写

假设一共有5个班,每个班4个组,每组3个人

int arr5[3] = {

{{1,2,3},{4,5,6},{7,8,9},{11,12,14}},
{{11,12,13},{14,15,16},{17,18,19},{111,112,114}},
{{21,22,23},{24,25,26},{27,28,29},{211,212,214}},
{{31,32,33},{34,35,36},{37,38,39},{311,312,314}},
{{41,42,43},{44,45,46},{47,48,49},{411,412,414}},

};

编译器如何分配空间?

如果获取第2个班级、第3组、第2个人的年龄: arr[1][2][1]
编译器的计算: arr[1*4*3 + 2*3 + 1]

如果获取第4个班级、第4组、第3个人的年龄: arr[3][3][2]
编译器如何计算?

arr[3*4*3 + 3*3 + 3]

C语言9 数组

数组的定义

数组定义的格式:


数据类型 变量名[常亮]; //为什么不能使变量?
//因为在声明的时候 编译器需要知道数组的长度,分配相应大小的内存

数组的初始化

  1. 方式1:

    int arr[10] = {0,0,0,0,0,0,0,0,0,0};
  2. 方式2:

    int arr[] = {1,2,3,4,5,6,7,8,9,10};

对应汇编


#include "stdafx.h"
int main(int argc, char* argv[])
{
    int age[10] = {1,2,3,4,5,6,7,8,9,10};
    return 0;
}
8:        int age[10] = {1,2,3,4,5,6,7,8,9,10};
00401028 C7 45 D8 01 00 00 00 mov         dword ptr [ebp-28h],1
0040102F C7 45 DC 02 00 00 00 mov         dword ptr [ebp-24h],2
00401036 C7 45 E0 03 00 00 00 mov         dword ptr [ebp-20h],3
0040103D C7 45 E4 04 00 00 00 mov         dword ptr [ebp-1Ch],4
00401044 C7 45 E8 05 00 00 00 mov         dword ptr [ebp-18h],5
0040104B C7 45 EC 06 00 00 00 mov         dword ptr [ebp-14h],6
00401052 C7 45 F0 07 00 00 00 mov         dword ptr [ebp-10h],7
00401059 C7 45 F4 08 00 00 00 mov         dword ptr [ebp-0Ch],8
00401060 C7 45 F8 09 00 00 00 mov         dword ptr [ebp-8],9
00401067 C7 45 FC 0A 00 00 00 mov         dword ptr [ebp-4],0Ah
//因为在函数内。所以分配在堆栈中,这里使用的就是ebp寻址

数组的内存分配

分辨定义char/short/int 类型的数组,观察ESP中减少了多少

前面讲过,在32位操作系统中 char类型分配的空间仍然是32位 跟int一样

本机宽度,在计算机系统中32位系统中,一次处理4字节速度最快。所以处理一次性处理本机宽度的数据最快
本机32位也就是4字节,所以会出现4字节对齐的情况


//本机宽度 32  4字节  4字节对齐
char age[10];
//4c - 40 =C 也就是十进制的12,char占用一个字节,需要10个字节,但是因为4字节对齐,所以分配的就是12个字节
short age[10];
//54 - 40 = 14 也就是十进制的 20 ,short占用两个字节,需要20个字节,20个字节正好符合4字节对齐,所以分配的就是20个字节

存入、读取数组中的值

读取数组中的某个成员的内容:


int x = 数组名[表达式]:
//读出数据
int a = age[0];
int b = age[1];

给数组中某个成员赋值:

数组名[表达式] = 表达式;
age[1] = 1;

特别说明
使用数组成员的时候[]可以使表达式。

c = arr[1];
c = arr[x];

数组的越界访问

int arr[10];
arr[10] = 100; //可以吗?结果会怎么样?

请看下面 缓冲区溢出

缓冲区溢出

#include "stdafx.h"
void Fun()
{
    while(1)
    {
        printf("我怎么会被调用列?\n");
    }
}
int main(int argc, char* argv[])
{
    int arr[8];
    arr[9] = (int)&Fun;
    return 0;
}

原理:
利用数组越界 造成缓冲区溢出

经过观察我们发现,这里的数组越界访问,造成了堆栈中返回地址被篡改为 Fun函数的地址,一旦执行到ret指令后,程序将会跳转到fun函数往下执行,也就进入了死循环

C语言8 循环语句

如何实现让某些语句按照一定的条件重复执行呢?

比如:打印从0 – N的值?
例子: goto语句

#include
#include
void MyPrint(int x)
{
    int i = 0;
B:
    printf("%d\n",i);
    i++;

Ubuntu安装最新版nginx

众所周知,Ubuntu 上官方源的更新速度一直是慢得令人发指的,很多人不得不自己编译 nginx,非常麻烦。

所以。我们直接用nginx官方源来安装。

增加源地址:

sudo vim /etc/apt/sources.list    

增加nginx官方源地址:

deb http://nginx.org/packages/mainline/ubuntu/ xenial nginx
deb-src http://nginx.org/packages/mainline/ubuntu/ xenial nginx

导入key

cd
wget http://nginx.org/keys/nginx_signing.key
sudo apt-key add nginx_signing.key

卸载旧版本

sudo apt remove nginx nginx-common nginx-full nginx-core

安装官方最新版

sudo apt update
sudo apt install nginx

开启服务和设置开启启动

sudo systemctl enable nginx
sudo systemctl start nginx

查看下状态:

systemctl status nginx

设置执行进程的用户账户

打开 /etc/nginx/nginx.conf 文件,发现第一行就是进程用户:nginx

这里改成www-data

搞定

修改Nginx 全局配置

http {
    include            mime.types;
    default_type       application/octet-stream;
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    access_log  /var/log/nginx/access.log  main;
    charset            UTF-8;
    sendfile           on;
    tcp_nopush         on;
    tcp_nodelay        on;
    keepalive_timeout  60;
    gzip               on;
    gzip_vary          on;
    gzip_comp_level    6;
    gzip_buffers       16 8k;
    gzip_min_length    1000;
    gzip_proxied       any;
    gzip_disable       "msie6";
    gzip_http_version  1.0;
    gzip_types         text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript image/svg+xml;
   include /etc/nginx/conf.d/*.conf;
}

站点配置模板

server {
    listen               443 ssl http2 fastopen=3 reuseport;
    # 如果你使用了 Cloudflare 的 HTTP/2 + SPDY 补丁,记得加上 spdy
    # listen               443 ssl http2 spdy fastopen=3 reuseport;
    server_name          www.imququ.com imququ.com;
    server_tokens        off;
    include              /home/jerry/www/nginx_conf/ip.blacklist;
    # https://imququ.com/post/certificate-transparency.html#toc-2
    ssl_ct               on;
    ssl_ct_static_scts   /home/jerry/www/scts;
    # 中间证书 + 站点证书
    ssl_certificate      /home/jerry/www/ssl/chained.pem;
    # 创建 CSR 文件时用的密钥
    ssl_certificate_key  /home/jerry/www/ssl/domain.key;
    # openssl dhparam -out dhparams.pem 2048
    # https://weakdh.org/sysadmin.html
    ssl_dhparam          /home/jerry/www/ssl/dhparams.pem;
    # https://github.com/cloudflare/sslconfig/blob/master/conf
    ssl_ciphers                EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
    # 如果启用了 RSA + ECDSA 双证书,Cipher Suite 可以参考以下配置:
    # ssl_ciphers              EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+ECDSA+AES128:EECDH+aRSA+AES128:RSA+AES128:EECDH+ECDSA+AES256:EECDH+aRSA+AES256:RSA+AES256:EECDH+ECDSA+3DES:EECDH+aRSA+3DES:RSA+3DES:!MD5;
    ssl_prefer_server_ciphers  on;
    ssl_protocols              TLSv1 TLSv1.1 TLSv1.2;
    ssl_session_cache          shared:SSL:50m;
    ssl_session_timeout        1d;
    ssl_session_tickets        on;
    # openssl rand 48 > session_ticket.key
    # 单机部署可以不指定 ssl_session_ticket_key
    ssl_session_ticket_key     /home/jerry/www/ssl/session_ticket.key;
    ssl_stapling               on;
    ssl_stapling_verify        on;
    # 根证书 + 中间证书
    # https://imququ.com/post/why-can-not-turn-on-ocsp-stapling.html
    ssl_trusted_certificate    /home/jerry/www/ssl/full_chained.pem;
    resolver                   114.114.114.114 valid=300s;
    resolver_timeout           10s;
    access_log                 /home/jerry/www/nginx_log/imququ_com.log;
    if ($request_method !~ ^(GET|HEAD|POST|OPTIONS)$ ) {
        return           444;
    }
    if ($host != 'imququ.com' ) {
        rewrite          ^/(.*)$  https://imququ.com/$1 permanent;
    }
    location ~* (robots\.txt|favicon\.ico|crossdomain\.xml|google4c90d18e696bdcf8\.html|BingSiteAuth\.xml)$ {
        root             /home/jerry/www/imququ.com/www/static;
        expires          1d;
    }
    location ^~ /static/uploads/ {
        root             /home/jerry/www/imququ.com/www;
        add_header       Access-Control-Allow-Origin *;
        set              $expires_time max;
        valid_referers   blocked none server_names *.qgy18.com *.inoreader.com feedly.com *.feedly.com www.udpwork.com theoldreader.com digg.com *.feiworks.com *.newszeit.com r.mail.qq.com yuedu.163.com *.w3ctech.com;
        if ($invalid_referer) {
            set          $expires_time -1;
            return       403;
        }
        expires          $expires_time;
    }
    location ^~ /static/ {
        root             /home/jerry/www/imququ.com/www;
        add_header       Access-Control-Allow-Origin *;
        expires          max;
    }
    location ^~ /admin/ {
        proxy_http_version       1.1;
        add_header               Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
        # DENY 将完全不允许页面被嵌套,可能会导致一些异常。如果遇到这样的问题,建议改成 SAMEORIGIN
        # https://imququ.com/post/web-security-and-response-header.html#toc-1
        add_header               X-Frame-Options DENY;
        add_header               X-Content-Type-Options nosniff;
        proxy_set_header         X-Via            QingDao.Aliyun;
        proxy_set_header         Connection       "";
        proxy_set_header         Host             imququ.com;
        proxy_set_header         X-Real_IP        $remote_addr;
        proxy_set_header         X-Forwarded-For  $proxy_add_x_forwarded_for;
        proxy_pass               http://127.0.0.1:9095;
    }
    location / {
        proxy_http_version       1.1;
        add_header               Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
        add_header               X-Frame-Options deny;
        add_header               X-Content-Type-Options nosniff;
        add_header               Content-Security-Policy "default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval' blob: https:; img-src data: https: http://ip.qgy18.com; style-src 'unsafe-inline' https:; child-src https:; connect-src 'self' https://translate.googleapis.com; frame-src https://disqus.com https://www.slideshare.net";
        add_header               Public-Key-Pins 'pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg="; pin-sha256="aef6IF2UF6jNEwA2pNmP7kpgT6NFSdt7Tqf5HzaIGWI="; max-age=2592000; includeSubDomains';
        add_header               Cache-Control no-cache;
        proxy_ignore_headers     Set-Cookie;
        proxy_hide_header        Vary;
        proxy_hide_header        X-Powered-By;
        proxy_set_header         X-Via            QingDao.Aliyun;
        proxy_set_header         Connection       "";
        proxy_set_header         Host             imququ.com;
        proxy_set_header         X-Real_IP        $remote_addr;
        proxy_set_header         X-Forwarded-For  $proxy_add_x_forwarded_for;
        proxy_pass               http://127.0.0.1:9095;
    }
}
server {
    server_name       www.imququ.com imququ.com;
    server_tokens     off;
    access_log        /dev/null;
    if ($request_method !~ ^(GET|HEAD|POST)$ ) {
        return        444;
    }
    location ^~ /.well-known/acme-challenge/ {
        alias         /home/jerry/www/challenges/;
        try_files     $uri =404;
    }
    location / {
        rewrite       ^/(.*)$ https://imququ.com/$1 permanent;
    }
}

以上配置来自于 imququ的 本博客 Nginx 配置之完整篇

C语言7 switch语句为什么高效

switch语句的定义

语法

switch(表达式)
{
    case 常亮表达式1:
        语句;
        break;
    case 常亮表达式:
        语句;
        break;
    case 常亮表达式:
        语句;
        break;
     ......
    default:
        语句;
        break;
}

需要注意的点

  1. 表达式结束不能使浮点数
  2. case后的值不能一样
  3. case后的值必须是常量
  • break:
  • break非常重要,当执行到一个分支后 如果没有break就会继续向下执行,遇到break才跳出switch语句
  • default 语句与位置无关,但是当default写在其他条件的前面时。如果没有break;,就会向下继续匹配

Switch语句与 if..else语句的区别:

  1. switch语句只能进行等值判断,而if..else可以进行区间判断
  2. switch语句的执行效率远远高于if..else 在分支条件比较多的情况下,这种趋势愈发明显

观察switch语句的反汇编,看看switch语句为啥效率高

为啥switch比if..else效率高

### 游戏中的switch语句

switch(x)
{
   case 1:
       printf("A \n");
       break;
   case 2:
       printf("B \n");
       break;
   case 3:
       printf("C \n");
       break;
   default:
       printf("default \n");
       break;
}

编译后反汇编代码

11:        switch(x)
12:    {
00401038 8B 45 08             mov         eax,dword ptr [ebp+8]
0040103B 89 45 FC             mov         dword ptr [ebp-4],eax
0040103E 83 7D FC 01          cmp         dword ptr [ebp-4],1
00401042 74 0E                je          MyPrint+32h (00401052)
00401044 83 7D FC 02          cmp         dword ptr [ebp-4],2
00401048 74 17                je          MyPrint+41h (00401061)
0040104A 83 7D FC 03          cmp         dword ptr [ebp-4],3
0040104E 74 20                je          MyPrint+50h (00401070)
00401050 EB 2D                jmp         MyPrint+5Fh (0040107f)
13:       case 1:
14:           printf("A \n");
00401052 68 30 00 42 00       push        offset string "A \n" (00420030)
00401057 E8 B4 00 00 00       call        printf (00401110)
0040105C 83 C4 04             add         esp,4
15:           break;
0040105F EB 2B                jmp         MyPrint+6Ch (0040108c)
16:       case 2:
17:           printf("B \n");
00401061 68 2C 00 42 00       push        offset string "B \n" (0042002c)
00401066 E8 A5 00 00 00       call        printf (00401110)
0040106B 83 C4 04             add         esp,4
18:           break;
0040106E EB 1C                jmp         MyPrint+6Ch (0040108c)
19:       case 3:
20:           printf("C \n");
00401070 68 28 00 42 00       push        offset string "C \n" (00420028)
00401075 E8 96 00 00 00       call        printf (00401110)
0040107A 83 C4 04             add         esp,4
21:           break;
0040107D EB 0D                jmp         MyPrint+6Ch (0040108c)
22:       default:
23:           printf("default \n");
0040107F 68 1C 00 42 00       push        offset string "default \n" (0042001c)
00401084 E8 87 00 00 00       call        printf (00401110)
00401089 83 C4 04             add         esp,4
24:           break;
25:    }
26:   }

### 区别

当调教比较少的时候 没啥区别 。但是当条件比较多的时候:


11:        switch(x)
12:    {
0040B818 8B 45 08             mov         eax,dword ptr [ebp+8]
0040B81B 89 45 FC             mov         dword ptr [ebp-4],eax
0040B81E 8B 4D FC             mov         ecx,dword ptr [ebp-4]
0040B821 83 E9 01             sub         ecx,1
0040B824 89 4D FC             mov         dword ptr [ebp-4],ecx
0040B827 83 7D FC 03          cmp         dword ptr [ebp-4],3
0040B82B 77 46                ja          $L539+0Fh (0040b873)
0040B82D 8B 55 FC             mov         edx,dword ptr [ebp-4]
0040B830 FF 24 95 91 B8 40 00 jmp         dword ptr [edx*4+40B891h]
13:       case 1:
14:           printf("A \n");
0040B837 68 34 0F 42 00       push        offset string "A \n" (00420f34)
0040B83C E8 CF 58 FF FF       call        printf (00401110)
0040B841 83 C4 04             add         esp,4
15:           break;
0040B844 EB 3A                jmp         $L539+1Ch (0040b880)
16:       case 2:
17:           printf("B \n");
0040B846 68 30 00 42 00       push        offset string "B \n" (00420030)
0040B84B E8 C0 58 FF FF       call        printf (00401110)
0040B850 83 C4 04             add         esp,4
18:           break;
0040B853 EB 2B                jmp         $L539+1Ch (0040b880)
19:       case 3:
20:           printf("C \n");
0040B855 68 2C 00 42 00       push        offset string "C \n" (0042002c)
0040B85A E8 B1 58 FF FF       call        printf (00401110)
0040B85F 83 C4 04             add         esp,4
21:           break;
0040B862 EB 1C                jmp         $L539+1Ch (0040b880)
22:       case 4:
23:           printf("D \n");
0040B864 68 28 00 42 00       push        offset string "D \n" (00420028)
0040B869 E8 A2 58 FF FF       call        printf (00401110)
0040B86E 83 C4 04             add         esp,4
24:           break;
0040B871 EB 0D                jmp         $L539+1Ch (0040b880)
25:       default:
26:           printf("default \n");
0040B873 68 1C 00 42 00       push        offset string "default \n" (0042001c)
0040B878 E8 93 58 FF FF       call        printf (00401110)
0040B87D 83 C4 04             add         esp,4
27:           break;
28:    }
29:   }

我们发现 生成一张内存表。
switch通过算法,直接一步算出地址 从表中找到需要跳转的地址

大表和小表

当生成的函数跳转地址表 每个成员有4个字节的时候 我们称之为 大表

总结:

  1. 即没有大表也没有小表:

    • case 项小于等于3项
    • case最大值和最小值差值>=255
  2. 有大表没小表:

    • case项大于3项并且大表项空隙小于等于6个
  3. 有大表有小表

    • case项大于3项并且大表项空隙大于6个并且case最大最小差值

C语言6 运算符和表达式

运算符与表达式

什么是运算符?什么是表达式?

int x,y;
x+y x-y  x>y x==y x=y

表达式的结果

(x+y)*(x-y)
char => short => int => float => double
/*如果x和 y类型 不同,最终结果按照上面结果类型转换*/

表达式不论怎么复杂。最终只有一个结果

不同类型运算结果类型证明的例子:

void main()
{
    char x =1;
    short y = 2;
    int z = x + y;
    return
}

对应汇编片段:

mov byte ptr [ebp-4],1
mov word ptr [ebp-8],offset main+20h(00401030)
movsx eax,byte ptr [ebp-4]
movsx ecx,word ptr [ebp-8]
add  eax,ecx
mov dword ptr [ebp-0Ch],eax

运算符

算术运算符

+   -   *   /   %   ++  --
加  减  乘  除  取余  自加 自减
int i = 1;

++i 和 i++

/*i++和++i的区别
i++ 先运算再自加
++i 先自加再运算*/
++i;
mov eax,dword ptr [ebp-4]
add eax,1
mov dword ptr [ebp-4],eax
i++;
mov eax,dword ptr [ebp-4]
add eax,1
mov dword ptr [ebp-4],eax
这俩其实是一样的!!但是为啥有这个 前后的区别呢?
往下看~~~
printf("%d \n",i++);
mov eax,dword ptr [ebp-4]
mov dword ptr [ebp-8],eax
mov ecx,dword ptr[ebp-8]
push ecx
push offset string "%d \n" (0042201c)
mov edx,dword ptr [ebp-4]
add edx,1
mov dword ptr [ebp-4],edx
call printf(00401070)
所以i++
就是先压入 i的值之后,才会 +1
也就是先让函数使用i运算后,才会+1
反之亦然 ++i 则是先+1 再运算

关系运算符

<      >=  ==  !=
关系运算符的值只能是0或1
关系运算符的值为真时,结果值都为1
关系运算符的值为假时,结果值都为0

逻辑运算符

!   %%  ||
x>y && xy || x   ~   |   ^    &
左移 右移  非   或  异或  与

赋值运算符:

= 拓展赋值

条件运算符

?:

C语言常用函数

sqrt

求给定值的平方根

sqrt() 用来求给定值的平方根,其原型为:

    double sqrt(double x);

【参数】x 为要计算平方根的值。

如果 x < 0,将会导致 domain error 错误,并把全局变量 errno 的值为设置为 EDOM。

【返回值】返回 x 平方根。

Exp:

#include
main(){
    double root;
    root = sqrt(200);
    printf("answer is %f\n", root);
}

pow

求x的y次方(次幂)

pow() 函数用来求 x 的 y 次幂(次方),其原型为:

    double pow(double x, double y);

pow()用来计算以 x 为底的 y 次方值,然后将结果返回。设返回值为 ret,则 ret = xy

可能导致错误的情况:

  • 如果底数 x 为负数并且指数 y 不是整数,将会导致 domain error 错误。
  • 如果底数 x 和指数 y 都是 0,可能会导致 domain error 错误,也可能没有;这跟库的实现有关。
  • 如果底数 x 是 0,指数 y 是负数,可能会导致 domain error 或 pole error 错误,也可能没有;这跟库的实现有关。
  • 如果返回值 ret 太大或者太小,将会导致 range error 错误。

错误代码:

  • 如果发生 domain error 错误,那么全局变量 errno 将被设置为 EDOM;
  • 如果发生 pole error 或 range error 错误,那么全局变量 errno 将被设置为 ERANGE。

Exp:

#include
#include
int main ()
{
    printf ("7 ^ 3 = %f\n", pow (7.0, 3.0) );
    printf ("4.73 ^ 12 = %f\n", pow (4.73, 12.0) );
    printf ("32.01 ^ 1.54 = %f\n", pow (32.01, 1.54) );
    return 0;
}

fabs

求浮点数的绝对值

fabs() 函数用来求浮点数的绝对值。在TC中原型为:

    float fabs(float x);

在VC6.0中原型为:

    double fabs( double x );

【参数】x 为一个浮点数。

【返回值】计算|x|,当x不为负时返回 x,否则返回 -x。

Exp:
求任意一个双精度数的绝对值。

#include
#include
#include
int main(void)
{
    char c;
    float i=-1;
    /*提示用户输入数值类型*/
    printf("I can get the float number's absolute value:\n");
    scanf("%f",&i);
    while(1)/*循环*/
    {
        printf("%f\n",fabs(i));/*求双精度绝对值并格式化*/
        scanf("%f",&i);/*等待输入*/
    }
    system("pause");
    return 0;
}

abs

求绝对值(整数)

abs()用来计算参数j 的绝对值,然后将结果返回。
定义:

int abs (int j);

返回值:返回参数j 的绝对值结果。

Exp:

#ingclude
main(){
    int ansert;
    answer = abs(-12);
    printf("|-12| = %d\n", answer);
}

atof

将字符串转换为double(双精度浮点数)

函数 atof() 用于将字符串转换为双精度浮点数(double),其原型为:

double atof (const char* str);

atof() 的名字来源于 ascii to floating point numbers 的缩写,它会扫描参数str字符串,跳过前面的空白字符(例如空格,tab缩进等,可以通过 isspace() 函数来检测),直到遇上数字或正负符号才开始做转换,而再遇到非数字或字符串结束时(‘0’)才结束转换,并将结果返回。参数str 字符串可包含正负号、小数点或E(e)来表示指数部分,如123. 456 或123e-2。

【返回值】返回转换后的浮点数;如果字符串 str 不能被转换为 double,那么返回 0.0

温馨提示:ANSI C 规范定义了 stof()、atoi()、atol()、strtod()、strtol()、strtoul() 共6个可以将字符串转换为数字的函数,大家可以对比学习;使用 atof() 与使用 strtod(str, NULL) 结果相同。另外在 C99 / C++11 规范中又新增了5个函数,分别是 atoll()、strtof()、strtold()、strtoll()、strtoull(),在此不做介绍,请大家自行学习。

Exp:

#include
#include
int main(){
    char *a = "-100.23",
         *b = "200e-2",
         *c = "341",
         *d = "100.34cyuyan",
         *e = "cyuyan";
    printf("a = %.2f\n", atof(a));
    printf("b = %.2f\n", atof(b));
    printf("c = %.2f\n", atof(c));
    printf("d = %.2f\n", atof(d));
    printf("e = %.2f\n", atof(e));
    system("pause");
    return 0;
}

atoi

atoi() 函数用来将字符串转换成整数(int),其原型为:

int atoi (const char * str);

【函数说明】atoi() 函数会扫描参数 str 字符串,跳过前面的空白字符(例如空格,tab缩进等,可以通过 isspace() 函数来检测),直到遇上数字或正负符号才开始做转换,而再遇到非数字或字符串结束时(‘0’)才结束转换,并将结果返回。

【返回值】返回转换后的整型数;如果 str 不能转换成 int 或者 str 为空字符串,那么将返回 0。

Exp:

#include
#include
int main ()
{
    int i;
    char buffer[256];
    printf ("Enter a number: ");
    fgets (buffer, 256, stdin);
    i = atoi (buffer);
    printf ("The value entered is %d.", i);
    system("pause");
    return 0;
}

strcat

strcat() 函数用来连接字符串,其原型为:

    char *strcat(char *dest, const char *src);

【参数】dest 为目的字符串指针,src 为源字符串指针。

strcat() 会将参数 src 字符串复制到参数 dest 所指的字符串尾部;dest 最后的结束字符 NULL 会被覆盖掉,并在连接后的字符串的尾部再增加一个 NULL。

注意:dest 与 src 所指的内存空间不能重叠,且 dest 要有足够的空间来容纳要复制的字符串。

【返回值】返回dest 字符串起始地址。

Exp:

#include
#include
int main ()
{
    char str[80];
    strcpy (str,"these ");
    strcat (str,"strings ");
    strcat (str,"are ");
    strcat (str,"concatenated.");
    puts (str);
    return 0;
}

strcmp

strcmp() 用来比较字符串(区分大小写),其原型为:

    int strcmp(const char *s1, const char *s2);

【参数】s1, s2 为需要比较的两个字符串。

字符串大小的比较是以ASCII 码表上的顺序来决定,此顺序亦为字符的值。strcmp()首先将s1 第一个字符值减去s2 第一个字符值,若差值为0 则再继续比较下个字符,若差值不为0 则将差值返回。例如字符串”Ac”和”ba”比较则会返回字符”A”(65)和’b'(98)的差值(-33)。

【返回值】若参数s1 和s2 字符串相同则返回0。s1 若大于s2 则返回大于0 的值。s1 若小于s2 则返回小于0 的值。

注意:strcmp() 以二进制的方式进行比较,不会考虑多字节或宽字节字符;如果考虑到本地化的需求,请使用 strcoll() 函数。

Exp:

#include
main(){
    char *a = "aBcDeF";
    char *b = "AbCdEf";
    char *c = "aacdef";
    char *d = "aBcDeF";
    printf("strcmp(a, b) : %d\n", strcmp(a, b));
    printf("strcmp(a, c) : %d\n", strcmp(a, c));
    printf("strcmp(a, d) : %d\n", strcmp(a, d));
}

strcpy

字符串拷贝

strcpy()会将参数src 字符串拷贝至参数dest 所指的地址。

char *strcpy(char *dest, const char *src);

函数说明:strcpy()会将参数src 字符串拷贝至参数dest 所指的地址。

返回值:返回参数dest 的字符串起始地址。

附加说明:如果参数 dest 所指的内存空间不够大,可能会造成缓冲溢出(buffer Overflow)的错误情况,在编写程序时请特别留意,或者用strncpy()来取代。

Exp:

#include
main(){
    char a[30] = "string(1)";
    char b[] = "string(2)";
    printf("before strcpy() :%s\n", a);
    printf("after strcpy() :%s\n", strcpy(a, b));
}

strlen

strlen()函数用来计算字符串的长度,其原型为:

 unsigned int strlen (char *s);

【参数说明】s为指定的字符串。

strlen()用来计算指定的字符串s 的长度,不包括结束字符”0″。

【返回值】返回字符串s 的字符数。

注意一下字符数组,例如

    char str[100] = "http://see.xidian.edu.cn/cpp/u/biaozhunku/";

定义了一个大小为100的字符数组,但是仅有开始的11个字符被初始化了,剩下的都是0,所以 sizeof(str) 等于100,strlen(str) 等于11。

如果字符的个数等于字符数组的大小,那么strlen()的返回值就无法确定了,例如

    char str[6] = "abcxyz";

strlen(str)的返回值将是不确定的。因为str的结尾不是0,strlen()会继续向后检索,直到遇到’0’,而这些区域的内容是不确定的。

注意:strlen() 函数计算的是字符串的实际长度,遇到第一个’0’结束。如果你只定义没有给它赋初值,这个结果是不定的,它会从首地址一直找下去,直到遇到’0’停止。而sizeof返回的是变量声明后所占的内存数,不是实际长度,此外sizeof不是函数,仅仅是一个操作符,strlen()是函数。

Exp:

#include
#include
int main()
{
    char *str1 = "http://see.xidian.edu.cn/cpp/u/shipin/";
    char str2[100] = "http://see.xidian.edu.cn/cpp/u/shipin_liming/";
    char str3[5] = "12345";
    printf("strlen(str1)=%d, sizeof(str1)=%d\n", strlen(str1), sizeof(str1));
    printf("strlen(str2)=%d, sizeof(str2)=%d\n", strlen(str2), sizeof(str2));
    printf("strlen(str3)=%d, sizeof(str3)=%d\n", strlen(str3), sizeof(str3));
    return 0;
}

tolower

将大写字母转换为小写字母
这尼玛看名字就知道是转小写

定义函数:

int tolower(int c);

函数说明:若参数 c 为大写字母则将该对应的小写字母返回。

返回值:返回转换后的小写字母,若不须转换则将参数c 值返回。

Exp:

#include
main(){
    char s[] = "aBcDeFgH12345;!#$";
    int i;
    printf("before tolower() : %s\n", s);
    for(i = 0; i < sizeof(s); i++)
        s[i] = tolower(s[i]);
    printf("after tolower() : %s\n", s);
}

话说上次写了个小函数来转换 其实大写变小写就是 + 0x20

toupper

将小写字母转换为大写字母

定义函数:

int toupper(int c);

函数说明:若参数 c 为小写字母则将该对应的大写字母返回。

返回值:返回转换后的大写字母,若不须转换则将参数c 值返回。

Exp:

#include
main(){
    char s[] = "aBcDeFgH12345;!#$";
    int i;
    printf("before toupper() : %s\n", s);
    for(i = 0; i < sizeof(s); i++)
        s[i] = toupper(s[i]);
    printf("after toupper() : %s\n", s);
}

C语言4 -C语言整数和浮点数

C语言数据类型

基本类型

整数类型

char、short、int、long
char    8BIT    1字节     0~0xFF
short   16BIT   2字节     0~0xFFFF
int     32BIT   4字节     0~0xFFFFFFFF
long    32BIT   4字节     0~0xFFFFFFFF

特别说明:
int 在16位计算机中与short宽度一样,在32位以上的计算机中与long相同

存储格式
char x = 1;         //0000 0001  0x01
char x = -1;        //1111 1111 0xFF
(反码、补码、源码规则存储)
数据溢出
char x = 0xFF;      //1111 1111
char Y = 0X100;     //0001 0000 0000 (char最多8位 此处溢出了)

数据溢出,是吧高位舍弃还是低位舍弃?

经过实验,发现直接丢弃高位

有符号数与无符号数(signed、unsigned)
  1. 什么时候使用有符号数 无符号数

    sinned char x = ;   //0000 0000  0到127、-128到-1
    printf("&u \n",x); 按无符号数输出打印
    printf("&d \n",x); 按有符号数输出打印
    unsinned char x = ;   //0000 0000  0到255 
  2. 有符号数与无符号数的区别

    • 正确理解有符号数与无符号数
    • 扩展时与比较时才有区别

    扩展

    signed char x = -1; //0xFF 1111 1111
    int y =x; // 1111 1111 1111 1111 1111 1111 1111 1111 0xFFFF FFFF
    signed char x = 127; //0x7F 0111 1111
    int y =x; // 0000 0000 0000 0000 0000 0000 0111 1111 0xFFFF FFFF
    unsigned char x = -1;   // 0xFF 1111 1111
    int y = x; // 0000 0000 0000 0000 0000 0000 1110 1111 0x0000 00FF  

    比较

    unsigned char x = -1; //0xFF
    unsigned char y = 1;  //0X01
    if(x > y)
    {
        printf("x>y \n");  //成立
    }
    char x = -1;        //0XFF
    char y = 1;         //0X01
    if(x > y)
    {
        printf("x>y \n");  //不成立
    }

浮点类型

声明方式
float           4字节
double          8字节
long double     8字节(某些平台的编译器可能是16个字节)
赋值:
float x = 1.23;
double d = 2.34;
long double d = 2.34;
建议:
float x = 1.23F;
double d = 2.34;
long double d = 2.34L;
浮点类型的存储格式

float和double在存储方式上都是村从 IEEE 编码规范的

十进制整数转二进制

8.25转成浮点存储:整数部分

总结:
所有的整数一定可以完整转换成2进制

8.25转成浮点存储:小数部分

总结:
用二进制描述小数,不可能做到完全精确
就好比用10进制来表示1/3也不可能完全精确是一个道理。

将一个float型转化为内存存储格式的步骤为:
  1. 先将这个实数的绝对值化为二进制格式
  2. 将这个二进制格式的实数的小数点左移或者右移N位,直到小数点移动到第一个有效数字的右边
  3. 从小数点右边第一位开始数出二十三位数字放入第22到第0位
  4. 如果实数是正的,则第31位放入“0”,否则放入 “1”
  5. 如果n是左移得到的,说明指数是正的,第30位放入“1”,如果n是右移得到的或n=0,则第30位放入“0”
  6. 如果n是左移得到的,则n减去1后化为二进制,并在左边加“0”补足七位,放入第29到第23位
  7. 如果n是右移得到的或n=0,则将n化为二进制后在左边加“0”补足七位,再各位求反,再放入第29到第23位

麻痹太复杂了

8.25 -> 100.01  -> 1.00001 * 2的三次方(指数是3)
科学计数法
10      =   1 * 10一次方      指数:1
100     =   1 * 10的二次方    指数:2
1000    =   1 * 10的三次方    指数:3
填充表格(flot)
符号位(1)  指数部分(8)     尾数部分(23)
0         10000010       000 0100 0000 0000 0000 0000
16进制表示: 0x4104 0000
尾数部分:经过第一步转换后 8.25等于
1.00001 * 2的三次方(指数是3)
尾数直接从前往后放所以尾数是:
000 0100 0000 0000 0000 0000
指数部分:
首位表示小数点移动方向
向左移动则为1,向右为0
指数部分简单方法:
不论左移还是右移。一律吧指数 +127 然后取2进制
左移了三次,指数为3,3的二进制是11, 但是这里要减去1(不知道为啥??)
所以指数部分为 1000 0010

练习:
将0.25转换为内存中存储的二进制数

(简单方法,不管指数是正数还是负数,一律加127 转换二进制填进去。。)

0.25 = 1 * 2的-2次方  指数为-2 指数 加 127 = 125 = 01111101
尾数 都是0
所以是小数点向右移动 所以指数首位为0
向右移动两位  所以 指数剩下的为 10,这里要减去1 所以为1
结果:
0 10000001 000 0000 0000 0000 0000 0000
0 01111101 000
浮点类型的精度

float和double的精度是由尾数的位数来决定的
float : 2^23 = 8388608 一共7位,这意味着最多能有7位有效数字;
double : 2^52 = 4503599627370496 一共16位,这意味着最多能用16位有效数字;

C语言5 -字符和字符串

int x = 123;            //补码
int float f = 123.4F;   //IEEE编码
int i = 'A';            //神马情况???

我们在代码中写入

int i = 'A';

反编译后汇编就编程了

mov dword ptr ss:[esp-4],0x41

为什么会变成41呢?

字符类型

ASCII 表

(American Standard Code for Information Interchange 美国标准信息交换代码)
维基百科: https://zh.wikipedia.org/wiki/ASCII

字符类型

ASCII表最大 127 16进制 是 0x7F 也就是只需要一个字节
一个字节就够了,所以通常我们会使用:

char x ='A'来存这种符号

所以,很多书会称char为字符类型。
其实这种说法是错误的,会让很多初学者认为char就是用来存储字符的

转义字符

比如换行符可以写为

char i = '\n';
或者
char i = 10;

C语言中的内置函数

putchar(10);  // 将对应数从ASCII表中查出画在(打印) 控制台上(一次只能打印一个字符)
putchar('\n');

printf函数的使用

#include
void main()
{
    printf("Hello World!\n");   //打印字符串
    int x = 0xFFFFFFFF;
    printf("%d %u %x\n",x,x,x);   //打印证书
    //-1  4294967295  ffffffff
    float f = 3.1415F;
    printf("%6.2f\n",f);        //打印浮点数
    //3.14
}
占位符:
%d 有符号数形式打印
%u 无符号形式打印
%x 16进制形式打印
%{x.y}f 打印浮点数  x标志打印总长度 y 代表小数点后长度

字符串 一堆字符的ASCII对应的的值在内存中连续的串

printf函数调用前 push 字符串在内存中保存的位置

vc6 反汇编:

push offset string "Hello World!" (0042f6c)
call printf(0040d3f0)
add esp,4

字符串的结束标志在内存中 是 00
出现00代表字符串结束

字符串

char buffer[20] = "Hello World!";
printf("%s\n",buffer);
%s 字符串形式打印

中文字符

char buffer[20]= "Hello World!";
printf("%s\n",buffer);
反汇编代码
5:        char buffer[20]= "Hello World!";
00401028 A1 20 00 42 00       mov         eax,[string "Hello World!" (00420020)]
0040102D 89 45 EC             mov         dword ptr [ebp-14h],eax
00401030 8B 0D 24 00 42 00    mov         ecx,dword ptr [string "Hello World!"+4 (00420024)]
00401036 89 4D F0             mov         dword ptr [ebp-10h],ecx
00401039 8B 15 28 00 42 00    mov         edx,dword ptr [string "Hello World!"+8 (00420028)]
0040103F 89 55 F4             mov         dword ptr [ebp-0Ch],edx
00401042 A0 2C 00 42 00       mov         al,[string "Hello World!"+0Ch (0042002c)]
00401047 88 45 F8             mov         byte ptr [ebp-8],al
0040104A 33 C9                xor         ecx,ecx
0040104C 89 4D F9             mov         dword ptr [ebp-7],ecx
0040104F 66 89 4D FD          mov         word ptr [ebp-3],cx
00401053 88 4D FF             mov         byte ptr [ebp-1],cl
6:        printf("%s\n",buffer);
00401056 8D 55 EC             lea         edx,[ebp-14h]
00401059 52                   push        edx
0040105A 68 1C 00 42 00       push        offset string "%s\n" (0042001c)
0040105F E8 3C 00 00 00       call        printf (004010a0)
00401064 83 C4 08             add         esp,8

中文

char buffer[20] = "中国";
printf("%s\n",buffer);
反汇编
5:    char buffer[20] = "中国";
00401028 A1 20 00 42 00       mov         eax,[string "\xd6\xd0\xb9\xfa" (00420020)]
0040102D 89 45 EC             mov         dword ptr [ebp-14h],eax
00401030 8A 0D 24 00 42 00    mov         cl,byte ptr [string "Hello World!"+4 (00420024)]
00401036 88 4D F0             mov         byte ptr [ebp-10h],cl
00401039 33 D2                xor         edx,edx
0040103B 89 55 F1             mov         dword ptr [ebp-0Fh],edx
0040103E 89 55 F5             mov         dword ptr [ebp-0Bh],edx
00401041 89 55 F9             mov         dword ptr [ebp-7],edx
00401044 66 89 55 FD          mov         word ptr [ebp-3],dx
00401048 88 55 FF             mov         byte ptr [ebp-1],dl
6:    printf("%s\n",buffer);
0040104B 8D 45 EC             lea         eax,[ebp-14h]
0040104E 50                   push        eax
0040104F 68 1C 00 42 00       push        offset string "%s\n" (0042001c)
00401054 E8 47 00 00 00       call        printf (004010a0)
00401059 83 C4 08             add         esp,8

观察ASCII表中并没有中文字符

拓展 ASCII码表 (EASCII)

详见 维基百科 https://zh.wikipedia.org/wiki/EASCII

如何在计算机中存储中文

我们发现就算加上 EASCII 也不能表示中文 于是我们如何在计算机中存储中文呢?

计算机发明之后及后面很长一段时间,只能应用于美国及西方一些发达国家,ASCII能够很好满足用户的需求。但是当天朝也有了计算机之后,为了显示中文。必须设计一套编码规则用于汉字转换为计算机可以接受的数字系统的数

天朝专家把那些127号后的奇异符号们 (即EASCII)取消掉,规定:一个小于127的字符意义与原来相同,但是两个大于127的字符连在一起时,就表示一个汉字,这样我们就可以组合出大约7000多个简体汉字了

在这些编码里,连在ASCII里本来就有的数字、标点、字母都统统编了两个字节长的编码,这就是常说的”全角“字符,而原来在127号以下的那些就叫”半角“字符了。

上述编码规则就是GB2312或GB2312-80

GB2312或GB2312-80

  1. 两种编码可能使用相同的数字代表两个不同的符号
  2. 或者使用相同的数字代表不同的符号

这种编码方式有很大的弊端,当试用此种编码方式的数据,在其他国家使用的时候,如果其他国家使用类似的编码规则,那么数据就会失去原本的意义。

Unicode编码就是为了解决这个问题才出现的!

IIS、asp.net 中TTFB诡异的500ms时间

最近一个H5的app做优化。我写了个转发接口。每次请求都会慢300到500ms,匪夷所思

问题发现

最近H5做了重构。
由于接口的安全原因 不得不做了一个转发接口。
转发接口接受来自h5页面的http请求,解析参数,处理敏感数据,然后调用后端的api完成接口逻辑。

最近发现一个奇怪的问题。
每次打开页面 TTFB的速度莫名超过300ms。排查日志发现整个转发接口处理时间不超过20ms,而后端的api也不超过15ms,那么这300ms
但是iis日志中显示此次请求确实话费了320ms 那么这300ms到期哪去了?

时间去哪了

转发接口中所有io操作全部用的异步,比如调用后端api的http,文件的io,甚至从http上下文中读取json参数是也用的异步。如此说来着300ms 确实不知道哪里来的。而且多次调试发现处理时间确实也就20ms。

Session是罪魁祸首

最后各种查询得知

asp.net保证同时只处理同一个sessionid的一条请求

也就是说 用了session之后 无形中等于加了一把锁。

客户端的多个请求,哪怕是同时间发出的并发请求,也会被一条一条执行,一个执行完才会执行另一个。等于一个队列。难怪每次打开页面 最后一个请求的时间总是等于前面的时间之和。

解决

把程序中所有依赖session的实现全部去掉,改用redis或者memcache缓存解决,用户授权使用jwt等方式,不依赖session

最后来个图