背景:性能应该是功能的一个重要参考,特别是在大数据的背景之下!写SQL语句时如果仅考虑业务逻辑,而不去考虑语句效率问题,有可能导致严重的效率问题,导致功能不可用或者资源消耗过大。其中的一种情况是,处理每日增量数据的程序,实际执行过程中可能会进行全表扫描,效率与全量程序并无二致。

案例:

mio_log数据量:134,092,418条记录

freph_a01_fromtask3数据量:176,581,388条记录

生产系统上按照业务处理逻辑编写的SQL语句核心代码如下:

  1. SELECT (CASE
  2. WHEN c.in_force_dateISNOT NULL
  3. THEN (CASE
  4. WHEN a.mio_date>=c.in_force_dateTHENa.mio_date
  5. ELSE c.in_force_date
  6. END )
  7. WHEN c.in_force_dateISNULL THEN (CASE
  8. WHEN a.mio_date>=a.plnmio_dateTHENa.mio_date
  9. ELSE a.plnmio_date
  10. END )
  11. ELSE a.mio_date
  12. END ) mio_date
  13. FROM   dbo.mio_loga
  14. INNER JOIN dbo.freph_a01_fromtask3c
  15. ON a.cntr_no = c.cntr_no
  16. AND a.pol_code=c.pol_code
  17. WHERE  ((c.in_force_dateISNOT NULL
  18. AND((CASE
  19. WHEN a.mio_date>=c.in_force_dateTHENa.mio_date
  20. ELSE c.in_force_date
  21. END ) BETWEEN @stat_begindateAND@stat_enddate))
  22. OR(c.in_force_dateISNULL
  23. AND((CASE
  24. WHEN a.mio_date>=a.plnmio_dateTHENa.mio_date
  25. ELSE a.plnmio_date
  26. END ) BETWEEN @stat_begindateAND@stat_enddate)) )

导致虽然mio_log表的mio_date、plnmio_date字段,以及freph_a01_fromtask3表的in_force_date字段上均有索引,但是由于两表不同字段进行CASE WHEN比较,执行计划为聚集索引扫描:

优化思路:

由于mio_log表的mio_date、plnmio_date字段,以及freph_a01_fromtask3表的in_force_date字段上均有索引,可先通过单个mio_date、in_force_date、plnmio_date索引取出增量时间段数据,在增量数据上进行不同表、字段的比对。

  1. SELECT (CASE
  2. WHEN in_force_date IS NOT NULL
  3. THEN ( CASE
  4. WHEN mio_date >= in_force_dateTHENmio_date
  5. ELSE in_force_date
  6. END )
  7. WHEN in_force_date IS NULL
  8. THEN ( CASE
  9. WHEN mio_date >= plnmio_dateTHENmio_date
  10. ELSE plnmio_date
  11. END )
  12. ELSE mio_date
  13. END )               mio_date
  14. from(
  15. SELECT a.mio_date,
  16. c.in_force_date,
  17. a.plnmio_date,
  18. a.MIO_LOG_ID
  19. FROM   dbo.mio_loga
  20. INNER JOIN dbo.freph_a01_fromtask3c
  21. ON a.cntr_no = c.cntr_no
  22. ANDa.pol_code=c.pol_code
  23. WHERE
  24. a.mio_dateBETWEEN@stat_begindateAND@stat_enddate
  25. union
  26. SELECT a.mio_date,
  27. c.in_force_date,
  28. a.plnmio_date,
  29. a.MIO_LOG_ID
  30. FROM   dbo.mio_loga
  31. INNER JOIN dbo.freph_a01_fromtask3c
  32. ON a.cntr_no = c.cntr_no
  33. ANDa.pol_code=c.pol_code
  34. WHERE
  35. c.in_force_dateBETWEEN@stat_begindateAND@stat_enddate
  36. union
  37. SELECT a.mio_date,
  38. c.in_force_date,
  39. a.plnmio_date,
  40. a.MIO_LOG_ID
  41. FROM   dbo.mio_loga
  42. INNER JOIN dbo.freph_a01_fromtask3c
  43. ON a.cntr_no = c.cntr_no
  44. ANDa.pol_code=c.pol_code
  45. WHERE
  46. a.plnmio_dateBETWEEN@stat_begindateAND@stat_enddate
  47. ) T
  48. WHERE  ((in_force_dateIS NOT NULL
  49. AND((CASE
  50. WHEN mio_date>= in_force_dateTHENmio_date
  51. ELSE in_force_date
  52. END ) BETWEEN @stat_begindateAND@stat_enddate))
  53. OR(in_force_dateIS NULL
  54. AND((CASE
  55. WHEN mio_date>= plnmio_dateTHENmio_date
  56. ELSE plnmio_date
  57. END ) BETWEEN @stat_begindateAND@stat_enddate)) )

该语句存在两个问题:

1.       如果子查询中mio_log、freph_a01_fromtask3没有主键,则需通过ROWID标识不同记录,即如果没有主键,可以通过ROWID进行替换。

ROWID这个概念在Oracle中非常重要,使用也非常广泛,其意义如下:

ROWIDPseudocolumn

Foreach row in the database, the ROWID pseudocolumn returns the address of therow. oracle Database rowid values contain information necessary to locate arow:

·         The dataobject number of the object

·         The datablock in the datafile in which the row resides

·         The positionof the row in the data block (first row is 0)

·         The datafilein which the row resides (first file is 1). The file number is relative to thetablespace.

SQLServer中并没有ROWID这个概念, SQL Server2008及以后版本中%%physloc%%虚拟列与ROWID最相近,信息如下:

The closest equivalent tothis in SQL Server is the rid which has three componentsFile:Page:Slot.

In SQL Server 2008 it ispossible to use the undocumented and unsupported %%physloc%% virtual column to see this. Thisreturns a binary(8) value with the Page ID in the firstfour bytes, then 2 bytes for File ID, followed by 2 bytes for the slot locationon the page.

The scalar function sys.fn_PhysLocFormatter or the sys.fn_PhysLocCracker TVF can be used to convert this into amore readable form.

  1. CREATE TABLET(XINT);
  2. INSERT INTOTVALUES(1),(2)
  3. SELECT %%physloc%%AS[%%physloc%%],
  4. sys.fn_PhysLocFormatter(%%physloc%%)AS[File:Page:Slot]
  5. FROM T

%%physloc%%

File:Page:Slot

0x7600000001000000

(1:118:0)

0x7600000001000100

(1:118:1)

Note that this is not leveraged by the queryprocessor. Whilst it is possible to use this in a WHERE clause

  1. SELECT *FROMT
  2. WHERE %%physloc%%=0x7600000001000000

SQL Server will not directly seek to thespecified row. Instead it will do a full table scan, evaluate %%physloc%% foreach row and return the one that matches (if any do).

2.     该语句有parameter sniffing问题:

当使用存储过程的时候,总是要使用到一些变量。变量有两种,一种是在存储过程的外面定义的,当调用存储过程的时候,必须要给它代入值,SQLServer在编译时知道它的值是多少。还有一种变量是在存储过程里面定义的。它的值是在存储过程的语句执行过程中得到的。对这种本地变量,SQLServer在编译时不知道它的值是多少。

SQLServer在处理存储过程时,为了节省编译时间,是一次编译多次使用的。那么计划重用就有两个潜在问题:

(1) 对于第一类变量,根据第一次运行时带入的值生成的执行计划,是不是就能够适合所有可能的变量值?

(2) 对于第二类本地变量,SQL Server在编译时并不知道它的值是多少,那怎么选择“合适”的执行计划?

parametersniffing”问题的定义:因为语句的执行计划对变量值很敏感,而导致重用执行计划会遇到性能问题。本地变量做出来的执行计划是一种比较“中庸”的方法,一般不会有parameter sniffing那么严重,很多时候,它还是解决parametersniffing的一个候选方案。

解决parameter sniffing问题的方法:

(1) 用exec()方式运行动态SQL语句:如果在存储过程里不是直接运行语句,而是把语句带上变量,生成一个字符串,再让exec()命令多动态语句运行,那SQL Server就会在运行到这个语句的时候,对动态语句进行编译。这时,SQLServer已经知道了变量的值,会根据值生成优化的执行计划,从而绕过parametersniffing问题。

(2) 使用本地变量:如果把变量值赋给一个本地变量,SQLServer在编译的时候是没有办法知道这个本地变量的值的。所以它会根据表格里数据的一般分布情况“猜测”一个返回值。不管用户在调用存储过程的时候带入的变量值是多少,做出来的执行计划都是一样的。而这样的执行计划一般比较“中庸”,不会是最优的执行计划,但是对大多数变量值来讲,也不会是一个很差的执行计划。该方法的好处是保持了存储过程的优点,缺点是要修改存储过程,而执行计划也不是最优的。

(3) 在语句里使用query hint指定执行计划:

在SELECT、INSERT、UPDATE、DELETE语句最后,可以加一个“Option(<query_hint>)”子句,对SQL Server将要生成的执行计划进行指导。目前的query_hint很强大,有十几种hint。完整的定义如下:

  1. <query_hint>::=
  2. { {HASH| ORDER } GROUP
  3. | {CONCAT| HASH | MERGE} UNION
  4. | {LOOP| MERGE | HASH} JOIN
  5. | FASTnumber_rows
  6. | FORCEORDER
  7. | MAXDOPnumber_of_processors
  8. | OPTIMIZEFOR( @vaariable_name= literal_constant[ , ...n ])
  9. | PARAMETERIZATION{SIMPLE | FORCED }
  10. | RECOMPILE
  11. | ROBUSTPLAN
  12. | KEEPPLAN
  13. | KEEPFIXEDPLAN
  14. | EXPANDVIEWS
  15. | MAXRECURSIONnumber
  16. | USEPLANN'xml_plan'
  17. }

这些hint的用途不一样。有些是引导执行计划使用什么样的运算的,例如{HASH| ORDER } GROUP、{CONCAT |HASH | MERGE} UNION、{LOOP| MERGE|HASH} JOIN。有些是防止重编译的,例如PARAMETERIZATION{SIMPLE | FORCED }、KEEPPLAN、KEEPFIXEDPLAN,有些是强制重编译的,如RECOMPILE。有些是影响执行计划的选择的,如FASTnumber_rows、FORCEORDER、MAXDOPnumber_of_processors、OPTIMIZEFOR( @vaariable_name= literal_constant[ , ...n ]),它们是和在不同的场合。具体定义参见SQL Server联机帮助。

为避免parameter sniffing问题,主要有以下几种常见query hint

(1)Recompile

Recompile这个查询提示告诉SQL Server,语句在每一次存储过程运行的时候,都要重新编译一下。这样就能够使SQL Server根据当前变量的值,选一个最好的执行计划。对前面的那个例子,我们可以这么改写。

  1. CREATE PROCNosniff_queryhint_recompile(@iINT)
  2. AS
  3. SELECT Count(b.SalesOrderID),
  4. Sum(p.Weight)
  5. FROM   dbo.SalesOrderHeader_testa
  6. INNER JOIN dbo.SalesOrderDetail_testb
  7. ON a.SalesOrderID=b.SalesOrderID
  8. INNER JOIN Production.Productp
  9. ON b.ProductID=p.ProductID
  10. WHERE  a.SalesOrderID=@i
  11. OPTION (recompile)
  12. go

和这种方法类似的,是在存储过程的定义里直接指定"recompile",也能达到避免parameter sniffing的效果。

  1. CREATE PROCNosniff_spcreate_recompile(@iINT)
  2. WITH recompile
  3. AS
  4. SELECT Count(b.SalesOrderID),
  5. Sum(p.Weight)
  6. FROM   dbo.SalesOrderHeader_testa
  7. INNER JOIN dbo.SalesOrderDetail_testb
  8. ON a.SalesOrderID=b.SalesOrderID
  9. INNER JOIN Production.Productp
  10. ON b.ProductID=p.ProductID
  11. WHERE  a.SalesOrderID=@i
  12. go

(2)   指定JOIN运算

  1. CREATE PROCNosniff_queryhint_joinhint(@iINT)
  2. AS
  3. SELECT Count(b.SalesOrderID),
  4. Sum(p.Weight)
  5. FROM   dbo.SalesOrderHeader_testa
  6. INNER JOIN dbo.SalesOrderDetail_testb
  7. ON a.SalesOrderID=b.SalesOrderID
  8. INNER hash JOIN Production.Productp
  9. ON b.ProductID=p.ProductID
  10. WHERE  a.SalesOrderID=@i
  11. go

(3)       OPTIMIZEFOR(@variable_name= literal_constant[ , …n] )

使用OPTIMIZE FOR 这个查询指导,就能够让SQL Server做到这一点。这是SQL 2005以后的一个新功能。

  1. create procNoSniff_QueryHint_OptimizeFor(@iint)as
  2. select count(b.SalesOrderID),sum(p.Weight)
  3. from dbo.SalesOrderHeader_testa
  4. inner joindbo.SalesOrderDetail_testb
  5. on a.SalesOrderID=b.SalesOrderID
  6. inner joinProduction.Productp
  7. on b.ProductID=p.ProductID
  8. where a.SalesOrderID=@i
  9. option (optimizefor(@i= 75124))
  10. go

(4)      Plan Guide

以上方法有个明显的局限性,就是徐要修改存储过程定义。有些时候没有应用开发组的许可,修改存储过程是不可以的。对用sp_executesql方式调用的指令,问题更大,因为这些指令可能是写在应用程序里面而不是SQLServer里。数据库管理员没有办法去修改应用程序。自SQLServer 2005以后,引入和完善了一种叫PlanGuide的功能,数据库管理员可以告诉SQLServer,当运行某个语句时,请数据库使用我制定的执行计划。这样就不许要修改存储过程或者应用。例如可以用下面的方法,在原来那个有parameter sniffing问题的存储过程”Sniff”上,解决sniffing问题。

  1. EXEC sp_create_plan_guide
  2. @name= N'Guide1',
  3. @stmt = N'select count(b.SalesOrderID),sum(p.Weight)
  4. from dbo.SalesOrderHeader_test a
  5. inner join dbo.SalesOrderDetail_test b
  6. on a.SalesOrderID = b.SalesOrderID
  7. inner join Production.Product p
  8. on b.ProductID = p.ProductID
  9. where a.SalesOrderID =@i',
  10. @type = N'OBJECT',
  11. @module_or_batch = N'Sniff',
  12. @params = NULL,
  13. @hints = N'OPTION (optimize for (@i = 75124))';
  14. go

由于以上两个问题,导致该方案在实际中并不是很好用

最优解决方案:

总体优化思路与上面的类似,只不过取增量范围是通过mio_log、in_force_date、plnmio_date字段上的索引取出mio_log_id范围,这三个索引取出的最大mio_log_id的最大值为@mio_log_id_max,最小的mio_log_id的最小值为@mio_log_id_min,那么增量数据范围可取出为mio_log_idbetween @mio_log_id_min and @mio_log_id_max。这是因为是瞬间完成的,同时通过mio_log_id取增量时能够确保走聚集索引。

具体解决方案如下:

  1. SELECT @mio_log_id_max3=Max(mio_log_id),
  2. @mio_log_id_min3 = Min(mio_log_id)
  3. FROM   dbo.freph_a01_fromtask3c(INDEX=i2_freph_a01_fromtask3)
  4. INNER loop JOIN mio_logaWITH(nolock)
  5. ON a.cntr_no = c.cntr_no
  6. AND a.pol_code=c.pol_code
  7. WHERE  c.in_force_dateBETWEEN@date_minAND @date_max
  8. SELECT @mio_log_id_max2=Max(mio_log_id),
  9. @mio_log_id_min2 = Min(mio_log_id)
  10. FROM   mio_log(INDEX=idx_mio_log_plnmio_date)
  11. WHERE  plnmio_dateBETWEEN@date_minAND @date_max
  12. SELECT @mio_log_id_max1=Max(mio_log_id),
  13. @mio_log_id_min1 = Min(mio_log_id)
  14. FROM   mio_log(INDEX=idx_mio_log_mio_date)
  15. WHERE  mio_dateBETWEEN@date_minAND @date_max
  16. SELECT @mio_log_id_max=dbo.F_find_max(@mio_log_id_max1,@mio_log_id_max2,@mio_log_id_max3)
  17. SELECT @mio_log_id_min=dbo.F_find_min(@mio_log_id_min1,@mio_log_id_min2,@mio_log_id_min3)
  18. SELECT (CASE
  19. WHEN in_force_date IS NOT NULL THEN
  20. (CASE
  21. WHEN mio_date>= in_force_dateTHENmio_date
  22. ELSE in_force_date
  23. END )
  24. WHEN in_force_date IS NULL THEN
  25. (CASE
  26. WHEN mio_date>= plnmio_dateTHENmio_date
  27. ELSE plnmio_date
  28. END )
  29. ELSE mio_date
  30. END ) mio_date
  31. FROM   (SELECTa.mio_date,
  32. a.plnmio_date,
  33. c.in_force_date
  34. FROM   dbo.mio_logaWITH(nolock)
  35. INNER JOIN dbo.freph_a01_fromtask3cWITH(nolock)
  36. ON a.cntr_no = c.cntr_no
  37. AND a.pol_code=c.pol_code
  38. WHERE  mio_log_id BETWEEN @mio_log_id_min AND @mio_log_id_max) T
  39. WHERE  ((t.in_force_dateISNOT NULL
  40. AND((CASE
  41. WHEN t.mio_date>=t.in_force_dateTHENt.mio_date
  42. ELSE t.in_force_date
  43. END ) BETWEEN @date_minAND@date_max ) )
  44. OR(t.in_force_dateISNULL
  45. AND((CASE
  46. WHEN t.mio_date>=t.plnmio_dateTHENt.mio_date
  47. ELSE t.plnmio_date
  48. END ) BETWEEN @date_minAND@date_max ) ) )

该方案在实施过程中有两个问题需要注意:

1.      通过非聚集索引取聚集索引键的最大最小值时,其自身生成的执行计划效率低下,需要通过query hint指导SQL Server优化器选择正确的执行计划:

  1. set statisticsioon
  2. set statisticstimeon
  3. declare @date_mindatetime
  4. declare @date_maxdatetime
  5. set @date_min='2013-07-15'
  6. set @date_max='2013-07-25'
  7. declare @mio_log_id_max1int
  8. declare @mio_log_id_min1int
  9. select @mio_log_id_max1=max(mio_log_id),@mio_log_id_min1=min(mio_log_id)
  10. from mio_log
  11. where mio_datebetween@date_minAND @date_max

执行计划如下为两个并行聚集索引扫描:

之所以通过聚集索引扫描来得到最大、最小mio_log_id,并不是进行完整的聚集索引扫描。SQL Server优化器以为从两头分别进行扫描,碰到第一个符合WHERE条件就返回的算法是最优的。而实验中通过参数得到的实际数据均分布在mio_log的最大端,得到最小的mio_log_id几乎就扫描了整个mio_log表,因而整个逻辑读为【到目前为止结果还没出来……,不等了】 。

该问题可以通过指导SQL Server优化器选择正确的执行计划解决:

  1. select @mio_log_id_max1=max(mio_log_id),@mio_log_id_min1=min(mio_log_id)
  2. from mio_log(index=idx_mio_log_mio_date)
  3. where mio_datebetween@date_minAND @date_max

执行计划如下:

逻辑读673,耗时215 ms。

2.      通过freph_a01_fromtask3表in_force_date字段获取mio_log表的mio_log_id时,其自身生成的执行计划效率低下,需要通过query hint指导SQL Server优化器选择正确的执行计划:

  1. SELECT @mio_log_id_max3=Max(mio_log_id),
  2. @mio_log_id_min3 = Min(mio_log_id)
  3. FROM   dbo.freph_a01_fromtask3c(INDEX=i2_freph_a01_fromtask3)
  4. INNER loop JOIN mio_logaWITH(nolock)
  5. ON a.cntr_no = c.cntr_no
  6. AND a.pol_code=c.pol_code
  7. WHERE  c.in_force_dateBETWEEN@date_minAND @date_max

另外,在逻辑优化过程中,还用到了索引覆盖、关联字段添加索引、脏读等技术。

参考资料:

1.      SQL Server ROWID: http://stackoverflow.com/questions/909155/equivalent-of-oracles-rowid-in-sql-server

2.      徐海蔚. Microsoft SQL Server企业级平台管理实践

case when 性能优化的更多相关文章

  1. Android性能优化-内存泄漏的8个Case

    1为什么要做性能优化? 手机性能越来越好,不用纠结这些细微的性能? Android每一个应用都是运行的独立的Dalivk虚拟机,根据不同的手机分配的可用内存可能只有(32M.64M等),所谓的4GB. ...

  2. C#中那些[举手之劳]的性能优化

    隔了很久没写东西了,主要是最近比较忙,更主要的是最近比较懒...... 其实这篇很早就想写了 工作和生活中经常可以看到一些程序猿,写代码的时候只关注代码的逻辑性,而不考虑运行效率 其实这对大多数程序猿 ...

  3. 【腾讯Bugly干货分享】跨平台 ListView 性能优化

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/FbiSLPxFdGqJ00WgpJ94yw 导语 精 ...

  4. CSS3与页面布局学习总结(八)——浏览器兼容与前端性能优化

    一.浏览器兼容 1.1.概要 世界上没有任何一个浏览器是一样的,同样的代码在不一样的浏览器上运行就存在兼容性问题.不同浏览器其内核亦不尽相同,相同内核的版本不同,相同版本的内核浏览器品牌不一样,各种运 ...

  5. 记录一次bug解决过程:可维护性和性能优化

    一.总结 使用某些变量的地方在2次以上的,强烈建议使用枚举值来维护变量,日后方便扩展. 查数据库的方法调用,能合并就净量去合并. 二.Bug描述 枚举变量的维护以及方法使用: public class ...

  6. 转载:SqlServer数据库性能优化详解

    本文转载自:http://blog.csdn.net/andylaudotnet/article/details/1763573 性能调节的目的是通过将网络流通.磁盘 I/O 和 CPU 时间减到最小 ...

  7. 【转载】 Spark性能优化指南——基础篇

    转自:http://tech.meituan.com/spark-tuning-basic.html?from=timeline 前言 开发调优 调优概述 原则一:避免创建重复的RDD 原则二:尽可能 ...

  8. Android性能优化典范第一季

    2015年伊始,Google发布了关于Android性能优化典范的专题,一共16个短视频,每个3-5分钟,帮助开发者创建更快更优秀的Android App.课程专题不仅仅介绍了Android系统中有关 ...

  9. Ceph性能优化总结(v0.94)

    优化方法论 做任何事情还是要有个方法论的,“授人以鱼不如授人以渔”的道理吧,方法通了,所有的问题就有了解决的途径.通过对公开资料的分析进行总结,对分布式存储系统的优化离不开以下几点: 1. 硬件层面 ...

随机推荐

  1. Tomcat默认连接超时时间

    秒=1小时 2. 在web.xml中通过参数指定: xml 代码 <session-config>         <session-timeout>30</sessio ...

  2. [PKUSC2018]主斗地

    暴搜 非常暴力的搜索,以至于我都不相信我能过. 方法是:暴力枚举所有牌型,然后暴力判断是否可行. 暴力枚举部分: 非常暴力: void dfs(int x,int l){ if(l==0){ flag ...

  3. mysql跨表删除多条记录

    Mysql可以在一个sql语句中同时删除多表记录,也可以根据多个表之间的关系来删除某一个表中的记录. 假定我们有两张表:Product表和ProductPrice表.前者存在Product的基本信息, ...

  4. spring boot 集成 redis lettuce(jedis)

    spring boot框架中已经集成了redis,在1.x.x的版本时默认使用的jedis客户端,现在是2.x.x版本默认使用的lettuce客户端 引入依赖 <!-- spring boot ...

  5. php 文件包含函数

    在实际开发中,常常需要把程序中的公用代码放到一个文件中,使用这些代码的文件只需要包含这个文件即可.这种方法有助于提高代码的重用性,给代码的编写与维护带来很大的便利.在PHP中, 有require.re ...

  6. PHP流程控制之goto语法

    自 PHP 5.3.0 起,还可以使用 goto 来跳出循环. 在本章开始的章节,我们讲解到一个故事,王同学每周往返,但有一个特例:直线电机滑台 项目失败后或者集团临时除知除外,他就可以不再这么每周往 ...

  7. web之大文件断点续传

    之前仿造uploadify写了一个HTML5版的文件上传插件,没看过的朋友可以点此先看一下~得到了不少朋友的好评,我自己也用在了项目中,不论是用户头像上传,还是各种媒体文件的上传,以及各种个性的业务需 ...

  8. apt-get 和dpkg命令

    软件包下载:apt-get 1.apt-get install vim  下载vim 2.apt-get upgrade vim 升级vim 3.apt-get update 列出更新 debian软 ...

  9. arts 打卡12周

    一 算法:  字符串转换整数 (atoi)   请你来实现一个 atoi 函数,使其能将字符串转换成整数. 首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止. 当我们寻找 ...

  10. #C++初学记录(字符串与指针操作库函数)

    测试程序 #include<iostream> #include<cstring> using namespace std; int a[204],b[204],lena,n; ...