C语言循环小技巧
写代码,有两类追求,一种是追求实用(Coder),一种是追求代码艺术(Artist)
我是那种追实用追腻了,偶然追一下艺术(就是偶然和艺术有一腿)的那种Coder 很多人,已经习惯了for(i=0; i<n; i++)这种单调的循环,虽然这的确的使用率最高,
但在特殊场合,特殊的循环写法,不但能提升循环的效率,还能使代码更精巧 1. 质数判断
对于这个,很多人可能会直接这样写:
int isPrime(int n) //函数返回1表示是质数,返回0表示不是质数
{
int i;
for (i = 2; i < n; i++)
if (n % i == 0)
break;
return i >= n;
} 又或者,有的人知道平方根的优化:
int isPrime(int n)
{
int i, s = (int)(sqrt((double)n) + 0.01);
for (i = 2; i <= s; i++)
if (n % i == 0)
break;
return i > s;
}
再或者,消除偶数:
int isPrime(int n)
{
int i, s = (int)(sqrt((double)n) + 0.01);
if (n <= 3) return 1;
if (n % 2 == 0) return 0;
for (i = 3; i <= s; i += 2)
if (n % i == 0)
break;
return i > s;
} 当然,这样还不是很够的话,我们可以考虑这个事实:
所有大于4的质数,被6除的余数只能是1或者5
比如接下来的5,7,11,13,17,19都满足 所以,我们可以特殊化先判断2和3
但后面的问题就出现了,因为并非简单的递增,从5开始是+2,+4,+2,+4,....这样递增的
这样的话,循环应该怎么写呢? 首先,我们定义一个步长变量step,循环大概是这样 for (i = 5; i <= s; i += step)
那么,就是每次循环,让step从2变4,或者从4变2
于是,可以这么写: #include <stdio.h>
#include <math.h> int isPrime(int n)
{
int i, s = (int)(sqrt((double)n) + 0.01), step = 4;
if (n <= 3) return 1;
if (n % 2 == 0) return 0;
if (n % 3 == 0) return 0;
for (i = 5; i <= s; i += step)
{
if (n % i == 0)
break;
step ^= 6;
}
return i > s;
} int main()
{
int n;
for (n = 2; n < 100; ++n) //找出 2 - 100 的质数并输出
{
if (isPrime(n)) printf("%d,", n);
}
getchar();
return 0;
} 如上代码,一个 step ^= 6; 完成step在2和4之间转换(这个 ^ 符号是C里的异或运算)
理由是,2化二进制是010,4是100,6是110,于是2异或4得到6:
2 ^ 4 => 6
6 ^ 2 => 4
6 ^ 4 => 2 于是利用异或,就可以构造这种步长在两个值之间来回变化的循环
思考题:前面说的是双值循环,那么如何构造三值或者四值循环? 2.菱形打印 很多人,打印菱形在控制台的思路是,把菱形上下拆分,分两段很接近的代码来打印,
其实这样代码很不好看,并且不好阅读
我们知道,要打印的图案是这种:
*
***
*****
***
* 满足上下对称,左右对称,那么,你能不能也弄一个二重循环,同样是对称的?
很简单,首先我们要抛开习惯性思维,for循环不一定要在0开始或者0结束
我们可以让循环从 -c 到 c ,这样不就轻松产生一个对称的吗?(只要取个绝对值)
我们把菱形的中心看成是坐标0,0,那么,会输出星号的坐标,是 |x| + |y| <= c 的点 由此可得
#include <stdio.h>
#define IABS(x) ( (x) >= 0 ? (x) : -(x) ) //定义一个计算绝对值的宏
void print(int size) // size是这个菱形的半径,直径会是size * 2 + 1
{
int x, y;
for (y = -size; y <= size; y++)
{
for (x = -size; x <= size; x++)
{
if ( IABS(x) + IABS(y) <= size ) //x和y各自的绝对值的和,即 |x| + |y| <= size
putchar('*');
else
putchar(' ');
}
putchar('\n');
}
} int main()
{
print(5); //输出一个半径为5的菱形
getchar();
return 0;
} 如果我需要得到空心菱形呢?非常非常简单,因为菱形边界上的点,满足的是|x| + |y| == c
所以,我们只要把那个if里的小于等于号,改成双等于号 == 就可以了 再类似地,如果我不要*号,我要最外层是字母A,然后里一层是B这样呢?即:
A
ABA
ABCBA
ABA
A 那么,我们只要在putchar那里做一个字符计算:
void print(int size) // size是这个菱形的半径,直径会是size * 2 + 1
{
int x, y;
for (y = -size; y <= size; y++)
{
for (x = -size; x <= size; x++)
{
if ( IABS(x) + IABS(y) <= size ) //x和y各自的绝对值的和,即 |x| + |y| <= size
putchar( 'A' + (size - IABS(x) - IABS(y)) ); //留意这里的计算方法
else
putchar(' ');
}
putchar('\n');
}
} 类似地,如果我们要打印的是X形:
* *
* *
*
* *
* *
同样可以利用这个思路完成,这题就作为思考题吧 3. 奇数阶幻方
所谓幻方(最基本的那种),就是横,竖,对角线上的数的和等于一个常数的数字方阵
4 3 8
9 5 1
2 7 6 以上这个图,有什么规律?容易写成代码吗? 我们把这个图,向右复制五次,向下复制三次,展开一下: 4 3 8 4 3 8 4 3 8 4 3 8 4 3 8
9 5 [1] 9 5 1 9 5 1 9 5 1 9 5 1
2 7 6 [2] 7 6 2 7 6 2 7 6 2 7 6
4 3 8 4 [3] 8 [4] 3 8 4 3 8 4 3 8
9 5 1 9 5 1 9 [5] 1 9 5 1 9 5 1
2 7 6 2 7 6 2 7 [6] 2 [7] 6 2 7 6
4 3 8 4 3 8 4 3 8 4 3 [8] 4 3 8
9 5 1 9 5 1 9 5 1 9 5 1 [9] 5 1
2 7 6 2 7 6 2 7 6 2 7 6 2 7 6 注意中括号数字的走向
怎么样,现在呢?
现在看起来显得规律性强了很多,但是,你会不会觉得循环还是不太好写?
我们如何从一个给定的n,直接得知它的坐标呢?
不难,找一下规律就可以发现对于任意的数值n+1有(以左上角为0,0坐标):
x = 2 + n + n / 3;
y = 1 + n - n / 3; 其实这个规律可以简单扩展到任意奇数阶幻方(以下size是奇数):
x = size / 2 + 1 + n + n / size; (注意这里的除法是取整除法,不带小数)
y = size / 2 + n - n / size; 这样,我们就可以把原来复杂的循环,化简成一重简单循环 于是有程序:
#include <stdio.h>
#define SIZE 5 //定义幻方阶数,这个数只能是奇数
int main()
{
int x, y, i, sqSize, hSize;
int sqMap[SIZE][SIZE];
sqSize = SIZE * SIZE;
hSize = SIZE / 2;
//计算1至SIZE * SIZE的数的位置并记录
for ( i = 0; i < sqSize; i++)
{
x = hSize + 1 + i + i / SIZE;
y = hSize + i - i / SIZE;
sqMap[y % SIZE][x % SIZE] = i + 1;
}
//以下是输出
for (y = 0; y < SIZE; y++)
{
for (x = 0; x < SIZE; x++)
printf("%4d", sqMap[y][x]);
puts("");
}
return 0;
} 这个比你网上能找到的很多求奇数阶幻方的代码都短小很多(不过网上较多称之为魔方阵,不知为何) 4. 字符串循环移位 问题,给你一个字符串,要求循环左移n位
比如对"abcdefg" 循环左移2位,我们要得到"cdefgab" 附加条件,不能使用连续辅助空间(包括动态分配),只能使用若干单个变量(即O(1)空间) 首先,我们知道,反转一个字符串操作("abcd"变"dcba"),是不需要额外数组辅助的,只要头尾数据交换就可以了
然而,可能你不知道,仅仅使用字符串反转可以实现字符串循环移位: //反转字符串,把st与ed所指向的中间的内容反转(包含st不包含ed)
void str_rev(char* st, char *ed)
{
for (--ed; st < ed; ++st, --ed)
{
char c;
c = *st; *st = *ed; *ed = c;
}
} //用三反转等效左移字符串(st与ed之间,包含st不包含ed的内容)
char* str_shl(char* st, char* ed, int n)
{
str_rev(st, &st[n]);
str_rev( &st[n], ed);
str_rev(st, ed);
return st;
} #include <stdio.h>
#include <string.h>
int main()
{
char str[] = "abcdefghijklmnopqrstuvwxyz";
puts( str_shl(str, str + strlen(str), 6) );
getchar();
return 0;
} 这里,如果要循环左移n位,只要把原来字符串分成两段,前n字符,和后面其它字符
两段分别反转,最后再整体反转,就实现了循环左移(如果先整体再两部分,就是循环右移) 而在那个字符串反转函数里,参与循环的,不再是int,而是两个指针,
为什么选择使用两个指针呢?如果你写一个str_rev(char* str, int len)的版本,相信你就明白了,这里不多废话
C语言循环小技巧的更多相关文章
- 嵌入式C语言优化小技巧
嵌入式C语言优化小技巧 1 概述 嵌入式系统是指完成一种或几种特定功能的计算机系统,具有自动化程度高,响应速度快等优点,目前已广泛应用于消费电子,工业控制等领域.嵌入式系统受其使用的硬件以及运行环境的 ...
- 【javascript】 for循环小技巧
最近在读[Jquery技术内幕],里面介绍了一种js for循环的实用写法. 一般写for循环是这么写的: var elemts = [1,2,3,4,5]; for(var i=0; i<el ...
- *C语言的小技巧
计算数组长度 ,,,,}; int Length=sizeof(a)/sizeof(int); 交换a和b的值,不借用辅助变量 a=a+b; b=a-b; a=a-b; 将0-9的字符转化为整数 '; ...
- C语言调试小技巧
经常看到有人介绍一些IDE或者像gdb这样的调试器的很高级的调试功能,也听人说过有些牛人做工程的时候就用printf来调试,不用特殊的调试器.特别是在代码经过编译器一些比较复杂的优化后,会变得“难以辨 ...
- HDU 5895 Mathematician QSC(矩阵乘法+循环节降幂+除法取模小技巧+快速幂)
传送门:HDU 5895 Mathematician QSC 这是一篇很好的题解,我想讲的他基本都讲了http://blog.csdn.net/queuelovestack/article/detai ...
- C语言中的调试小技巧
C语言中的调试小技巧 经常看到有人介绍一些IDE或者像gdb这样的调试器的很高级的调试功能,也听人说过有些牛人做工程的时候就用printf来调试,不用特殊的调试器.特别是在代码经过编译器一些比较复杂的 ...
- Python语言防坑小技巧
Python语言防坑小技巧 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.赋值即定义 1>.运行以下代码会出现报错 #!/usr/bin/env python #_*_ ...
- 一些Python的惯用法和小技巧:Pythonic
Pythonic其实是个模糊的含义,没有确定的解释.网上也没有过多关于Pythonic的说明,我个人的理解是更加Python,更符合Python的行为习惯.本文主要是说明一些Python的惯用法和小技 ...
- ( 译、持续更新 ) JavaScript 上分小技巧(三)
最近家里杂事较多,自学时间实在少的可怜,所以都在空闲时间看看老外写的内容,学习之外顺便翻译分享~等学习的时间充足些再写写自己的一些学习内容和知识点分析(最近有在接触的:复习(C#,SQL).(学习)T ...
随机推荐
- 给即将面临Noip的二班同学
给即将面临Noip的二班同学: 我们共同走过了一年,在这里,真正认识彼此…… 失落过,但更多是欢笑…… 或许我们班的信息学竞赛承受着巨大的压力,但正因为这样,我们才学会了坚持:或许我们得不到他人的认可 ...
- 排序算法 - 快速排序(Quick Sort)
算法思想 快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序.它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod). (1) 分治法的基本思想 ...
- Tomcat从零开始(十)Loader
第十课: 不知不觉就10篇blog了,说实话,我是第一次更这么长时间的Blog. 嗯,今天说说Loader,在以前的课程中,也就是内个能使用最初级的servlet的那一节,我们使用了URLClassL ...
- JAVA词汇大全
JAVA词汇大全 A B C D E F H I J L M O P R S T U V W A Abstract Window Toolkit(AWT)抽象窗体工具集 一个用本地图形组件实现的 ...
- 文件上传功能 -- jquery.form.js/springmvc
距离上一篇 文件上传下载样式 -- bootstrap(http://www.cnblogs.com/thomascui/p/5370947.html)已经三周时间了,期间一直考虑怎么样给大家提交一篇 ...
- iOS开发之AsyncSocket使用教程
用socket可以实现像QQ那样发送即时消息的功能.客户端和服务端需要建立长连接,在长连接的情况下,发送消息.客户端可以发送心跳包来检测长连接. 在iOS开发中使用socket,一般都是用第三方库As ...
- Session会话跟踪
用encodeURL重写URL public class SessionA extends HttpServlet { @Override protected void doGet(HttpServl ...
- virtualbox中新版本Ubuntu安装软件增强包后重启无限登录界面的解决办法
原来我虚拟机版本是4.2.10,装的Ubuntu3.3,因为版本过老使用出现了一些问题,于是换成14.04,安装成功,但是装增强包的时候,装完重启,无限登录界面,密码是对的. 看了网上的很多方法,什么 ...
- Android adb 命令图解
做了这么长时间的开发与管理,在命令上总是自见则过,往往却忽视了在其命令上的分享过程,所以现在稍微有点时间就把 其命令的相关操作来简单的扫盲一番吧,也系统通过这种方式去授之以渔而不是鱼,好了,我以图解的 ...
- C++的常量折叠(三)
背景知识 在开始之前先说一下符号表,这个编译器中的东西.下面看一下百度百科中的描述: 符号表是一种用于语言翻译器中的数据结构.在符号表中,程序源代码中的每个标识符都和它的声明或使用信息绑定在一起,比如 ...