循环结构 --> 双重for循环

双重for循环的格式

for(初始化表达式1; 条件表达式2; 修改初始值表达式3) {
    for(初始化表达式4; 条件表达式5; 修改初始值表达式6) {
        循环体;
    }
}

双重for循环的执行流程

执行表达式1 => 判断表达式2是否成立
		=> 若成立,则执行表达式4 => 判断表达式5是否成立
			=> 若成立,则执行循环体 => 执行表达式6 => 表达式5是否成立
			   => 若不成立,则内层循环结束 => 表达式3 => 表达式2是否成立
		=> 若不成立,则外层循环结束  

双重for循环的特点

  • 外层循环用于控制打印的行数,内层循环用于控制打印的列数,外层循环改一下,内层循环从头到尾跑一圈。
  • 在以后的开发中若需要打印多行多列时,需要使用双重循环。
  • 多重循环不宜嵌套太多层,否则效率很低。一般到三重循环即可,最常见的就是双重循环
package demo;

/*
    编程实现双重for循环的使用
 */
public class ForForTest {
	
	public static void main(String[] args) {
		
		// 1.使用for循环打印5行字符串内容"厉害了我的哥!"
		for(int i = 1; i <= 5; i++) {
			System.out.println("厉害了我的哥!");
		}
		
		System.out.println("------------------------------------");
		// 2.使用for循环打印5列字符串内容"厉害了我的哥!"
		for(int j = 1; j <= 5; j++) {
			System.out.print("厉害了我的哥! ");
		}
		System.out.println();
		
		System.out.println("------------------------------------");
		// 3.使用for循环打印5行5列字符串内容"厉害了我的哥!"
		// 外层循环主要用于控制打印的行数
		for(int i = 1; i <= 5; i++) {
			// 内层循环主要用于控制打印的列数
			for(int j = 1; j <= 5; j++) {
				System.out.print("厉害了我的哥! ");
			}
			System.out.println();
		}
		
		/*
		    厉害了我的哥!厉害了我的哥!厉害了我的哥!厉害了我的哥!厉害了我的哥!
		    厉害了我的哥!厉害了我的哥!厉害了我的哥!厉害了我的哥!厉害了我的哥!
		    厉害了我的哥! ...
		*/
	}
}
厉害了我的哥!
厉害了我的哥!
厉害了我的哥!
厉害了我的哥!
厉害了我的哥!
------------------------------------
厉害了我的哥! 厉害了我的哥! 厉害了我的哥! 厉害了我的哥! 厉害了我的哥! 
------------------------------------
厉害了我的哥! 厉害了我的哥! 厉害了我的哥! 厉害了我的哥! 厉害了我的哥! 
厉害了我的哥! 厉害了我的哥! 厉害了我的哥! 厉害了我的哥! 厉害了我的哥! 
厉害了我的哥! 厉害了我的哥! 厉害了我的哥! 厉害了我的哥! 厉害了我的哥! 
厉害了我的哥! 厉害了我的哥! 厉害了我的哥! 厉害了我的哥! 厉害了我的哥! 
厉害了我的哥! 厉害了我的哥! 厉害了我的哥! 厉害了我的哥! 厉害了我的哥!

案例题目一

使用双重for循环分别打印以下图案
在这里插入图片描述

package demo;

/*
    编程使用双重for循环打印星星图案
 */
public class ForForStarTest {
	
	public static void main(String[] args) {
		
		// 1.打印第一个星星图案
		// 外层循环主要用于控制打印的行数
		for(int i = 1; i <= 5; i++) {
			// 内层循环主要用于控制打印的列数
			for(int j = 1; j <= 5; j++) {
				System.out.print("*");
			}
			System.out.println();
		}
		
		System.out.println("--------------------------------------------");
		// 2.打印第二个星星图案
		// 外层循环主要用于控制打印的行数
		for(int i = 1; i <= 5; i++) {
			// 内层循环主要用于控制打印的列数  也就是当前行的列数与当前行的行数是相等关系
			for(int j = 1; j <= i; j++) {
				System.out.print("*");
			}
			System.out.println();
		}
		
		System.out.println("--------------------------------------------");
		// 3.打印第三个星星图案
		// 外层循环主要用于控制打印的行数
		for(int i = 1; i <= 5; i++) {
			// 内层循环主要用于控制打印的列数  也就是当前行的列数与当前行的行数相加为6的关系
			for(int j = 1; j <= 6-i; j++) {
				System.out.print("*");
			}
			System.out.println();
		}
		
		System.out.println("--------------------------------------------");
		// 4.打印第四个星星图案
		// 外层循环主要用于控制打印的行数
		for(int i = 1; i <= 5; i++) {
			// 控制空格的个数
			for(int j = 1; j <= 5-i; j++) {
				System.out.print(" ");
			}
			// 内层循环主要用于控制打印的列数  也就是当前行的列数与当前行的行数为 2*i-1 的关系
			for(int j = 1; j <= 2*i-1; j++) {
				System.out.print("*");
			}
			System.out.println();
		}
	}
}
*****
*****
*****
*****
*****
--------------------------------------------
*
**
***
****
*****
--------------------------------------------
*****
****
***
**
*
--------------------------------------------
    *
   ***
  *****
 *******
*********

案例题目二

使用双重for循环打印九九乘法表

package demo;

/*
    使用双重for循环打印九九乘法表
 */
public class ForForTableTest {
	
	public static void main(String[] args) {
		
		// 1.使用外层for循环控制打印的行数,一共9行
		outer:for(int i = 1; i <= 9; i++) {
			// 2.使用内层for循环控制打印的列数,最多9列,规律是:与当前行所在的行数相等
			for(int j = 1; j <= i; j++) {
				// 3.使用两个循环变量来拼接等式
				System.out.print(j + "*" + i + "=" + j*i + " ");
				// 4.当打印完毕6*6 = 36后结束整个打印
				if(6 == j) {
					//break; // 主要用于跳出循环,但该关键字只能跳出当前所在的循环
					break outer; // 表示可以跳出外层for循环
				}
			}
			System.out.println();
		}
	}
}
1*1=1 
1*2=2 2*2=4 
1*3=3 2*3=6 3*3=9 
1*4=4 2*4=8 3*4=12 4*4=16 
1*5=5 2*5=10 3*5=15 4*5=20 5*5=25 
1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36 
  • 3
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
文将对 Linux™ 程序员可以使用的内存管理技术进行概述,虽然关注的重点是 C 语言,但同样也适用于其他语言。文中将为您提供如何管理内存的细节,然后将进一步展示如何手工管理内存,如何使用引用计数或者内存池来半手工地管理内存,以及如何使用垃圾收集自动管理内存。 为什么必须管理内存 内存管理是计算机编程最为基本的领域之一。在很多脚本语言中,您不必担心内存是如何管理的,这并不能使得内存管理的重要性有一点点降低。对实际编程来说,理解您的内存管理器的能力与局限性至关重要。在大部分系统语言中,比如 C 和 C++,您必须进行内存管理。本文将介绍手工的、半手工的以及自动的内存管理实践的基本概念。 追溯到在 Apple II 上进行汇编语言编程的时代,那时内存管理还不是个大问题。您实际上在运行整个系统。系统有多少内存,您就有多少内存。您甚至不必费心思去弄明白它有多少内存,因为每一台机器的内存数量都相同。所以,如果内存需要非常固定,那么您只需要选择一个内存范围并使用它即可。 不过,即使是在这样一个简单的计算机中,您也会有问题,尤其是当您不知道程序的每个部分将需要多少内存时。如果您的空间有限,而内存需求是变化的,那么您需要一些方法来满足这些需求: 确定您是否有足够的内存来处理数据。 从可用的内存中获取一部分内存。 向可用内存池(pool)中返回部分内存,以使其可以由程序的其他部分或者其他程序使用。 实现这些需求的程序库称为 分配程序(allocators),因为它们负责分配和回收内存。程序的动态性越强,内存管理就越重要,您的内存分配程序的选择也就更重要。让我们来了解可用于内存管理的不同方法,它们的好处与不足,以及它们最适用的情形。 回页首 C 风格的内存分配程序 C 编程语言提供了两个函数来满足我们的三个需求: malloc:该函数分配给定的字节数,并返回一个指向它们的指针。如果没有足够的可用内存,那么它返回一个空指针。 free:该函数获得指向由 malloc 分配的内存片段的指针,并将其释放,以便以后的程序或操作系统使用(实际上,一些 malloc 实现只能将内存归还给程序,而无法将内存归还给操作系统)。 物理内存和虚拟内存 要理解内存在程序中是如何分配的,首先需要理解如何将内存从操作系统分配给程序。计算机上的每一个进程都认为自己可以访问所有的物理内存。显然,由于同时在运行多个程序,所以每个进程不可能拥有全部内存。实际上,这些进程使用的是 虚拟内存。 只是作为一个例子,让我们假定您的程序正在访问地址为 629 的内存。不过,虚拟内存系统不需要将其存储在位置为 629 的 RAM 中。实际上,它甚至可以不在 RAM 中 —— 如果物理 RAM 已经满了,它甚至可能已经被转移到硬盘上!由于这类地址不必反映内存所在的物理位置,所以它们被称为虚拟内存。操作系统维持着一个虚拟地址到物理地址的转换的表,以便计算机硬件可以正确地响应地址请求。并且,如果地址在硬盘上而不是在 RAM 中,那么操作系统将暂时停止您的进程,将其他内存转存到硬盘中,从硬盘上加载被请求的内存,然后再重新启动您的进程。这样,每个进程都获得了自己可以使用的地址空间,可以访问比您物理上安装的内存更多的内存。 在 32-位 x86 系统上,每一个进程可以访问 4 GB 内存。现在,大部分人的系统上并没有 4 GB 内存,即使您将 swap 也算上, 每个进程所使用的内存也肯定少于 4 GB。因此,当加载一个进程时,它会得到一个取决于某个称为 系统中断点(system break)的特定地址的初始内存分配。该地址之后是未被映射的内存 —— 用于在 RAM 或者硬盘中没有分配相应物理位置的内存。因此,如果一个进程运行超出了它初始分配的内存,那么它必须请求操作系统“映射进来(map in)”更多的内存。(映射是一个表示一一对应关系的数学术语 —— 当内存的虚拟地址有一个对应的物理地址来存储内存内容时,该内存将被映射。) 基于 UNIX 的系统有两个可映射到附加内存中的基本系统调用: brk: brk() 是一个非常简单的系统调用。还记得系统中断点吗?该位置是进程映射的内存边界。 brk() 只是简单地将这个位置向前或者向后移动,就可以向进程添加内存或者从进程取走内存。 mmap: mmap(),或者说是“内存映像”,类似于 brk(),但是更为灵活。首先,它可以映射任何位置的内存,而不单单只局限于进程。其次,它不仅可以将虚拟地址映射到物理的 RAM 或者 swap,它还可以将它们映射到文件和文件位置,这样,读写内存将对文件中的数据进行读写。不过,在这里,我们只关心 mmap 向进程添加被映射的内存的能力。 munmap() 所做的事情与 mmap() 相反。 如您所见, brk() 或者 mmap() 都可以用来向我们的进程添加额外的虚拟内存。在我们的例子中将使用 brk(),因为它更简单,更通用。 实现一个简单的分配程序 如果您曾经编写过很多 C 程序,那么您可能曾多次使用过 malloc() 和 free()。不过,您可能没有用一些时间去思考它们在您的操作系统中是如何实现的。本节将向您展示 malloc 和 free 的一个最简化实现的代码,来帮助说明管理内存时都涉及到了哪些事情。 要试着运行这些示例,需要先 复制本代码清单,并将其粘贴到一个名为 malloc.c 的文件中。接下来,我将一次一个部分地对该清单进行解释。 在大部分操作系统中,内存分配由以下两个简单的函数来处理: void *malloc(long numbytes):该函数负责分配 numbytes 大小的内存,并返回指向第一个字节的指针。 void free(void *firstbyte):如果给定一个由先前的 malloc 返回的指针,那么该函数会将分配的空间归还给进程的“空闲空间”。 malloc_init 将是初始化内存分配程序的函数。它要完成以下三件事:将分配程序标识为已经初始化,找到系统中最后一个有效内存地址,然后建立起指向我们管理的内存的指针。这三个变量都是全局变量: 清单 1. 我们的简单分配程序的全局变量 int has_initialized = 0; void *managed_memory_start; void *last_valid_address; 如前所述,被映射的内存的边界(最后一个有效地址)常被称为系统中断点或者 当前中断点。在很多 UNIX® 系统中,为了指出当前系统中断点,必须使用 sbrk(0) 函数。 sbrk 根据参数中给出的字节数移动当前系统中断点,然后返回新的系统中断点。使用参数 0 只是返回当前中断点。这里是我们的 malloc 初始化代码,它将找到当前中断点并初始化我们的变量: 清单 2. 分配程序初始化函数 /* Include the sbrk function */ #include void malloc_init() { /* grab the last valid address from the OS */ last_valid_address = sbrk(0); /* we don't have any memory to manage yet, so *just set the beginning to be last_valid_address */ managed_memory_start = last_valid_address; /* Okay, we're initialized and ready to go */ has_initialized = 1; } 现在,为了完全地管理内存,我们需要能够追踪要分配和回收哪些内存。在对内存块进行了 free 调用之后,我们需要做的是诸如将它们标记为未被使用的等事情,并且,在调用 malloc 时,我们要能够定位未被使用的内存块。因此, malloc 返回的每块内存的起始处首先要有这个结构: 清单 3. 内存控制块结构定义 struct mem_control_block { int is_available; int size; }; 现在,您可能会认为当程序调用 malloc 时这会引发问题 —— 它们如何知道这个结构?答案是它们不必知道;在返回指针之前,我们会将其移动到这个结构之后,把它隐藏起来。这使得返回的指针指向没有用于任何其他用途的内存。那样,从调用程序的角度来看,它们所得到的全部是空闲的、开放的内存。然后,当通过 free() 将该指针传递回来时,我们只需要倒退几个内存字节就可以再次找到这个结构。 在讨论分配内存之前,我们将先讨论释放,因为它更简单。为了释放内存,我们必须要做的惟一一件事情就是,获得我们给出的指针,回退 sizeof(struct mem_control_block) 个字节,并将其标记为可用的。这里是对应的代码: 清单 4. 解除分配函数 void free(void *firstbyte) { struct mem_control_block *mcb; /* Backup from the given pointer to find the * mem_control_block */ mcb = firstbyte - sizeof(struct mem_control_block); /* Mark the block as being available */ mcb->is_available = 1; /* That's It! We're done. */ return; } 如您所见,在这个分配程序中,内存的释放使用了一个非常简单的机制,在固定时间内完成内存释放。分配内存稍微困难一些。以下是该算法的略述: 清单 5. 主分配程序的伪代码 1. If our allocator has not been initialized, initialize it. 2. Add sizeof(struct mem_control_block) to the size requested. 3. start at managed_memory_start. 4. Are we at last_valid address? 5. If we are: A. We didn't find any existing space that was large enough -- ask the operating system for more and return that. 6. Otherwise: A. Is the current space available (check is_available from the mem_control_block)? B. If it is: i) Is it large enough (check "size" from the mem_control_block)? ii) If so: a. Mark it as unavailable b. Move past mem_control_block and return the pointer iii) Otherwise: a. Move forward "size" bytes b. Go back go step 4 C. Otherwise: i) Move forward "size" bytes ii) Go back to step 4 我们主要使用连接的指针遍历内存来寻找开放的内存块。这里是代码: 清单 6. 主分配程序 void *malloc(long numbytes) { /* Holds where we are looking in memory */ void *current_location; /* This is the same as current_location, but cast to a * memory_control_block */ struct mem_control_block *current_location_mcb; /* This is the memory location we will return. It will * be set to 0 until we find something suitable */ void *memory_location; /* Initialize if we haven't already done so */ if(! has_initialized) { malloc_init(); } /* The memory we search for has to include the memory * control block, but the users of malloc don't need * to know this, so we'll just add it in for them. */ numbytes = numbytes + sizeof(struct mem_control_block); /* Set memory_location to 0 until we find a suitable * location */ memory_location = 0; /* Begin searching at the start of managed memory */ current_location = managed_memory_start; /* Keep going until we have searched all allocated space */ while(current_location != last_valid_address) { /* current_location and current_location_mcb point * to the same address. However, current_location_mcb * is of the correct type, so we can use it as a struct. * current_location is a void pointer so we can use it * to calculate addresses. */ current_location_mcb = (struct mem_control_block *)current_location; if(current_location_mcb->is_available) { if(current_location_mcb->size >= numbytes) { /* Woohoo! We've found an open, * appropriately-size location. */ /* It is no longer available */ current_location_mcb->is_available = 0; /* We own it */ memory_location = current_location; /* Leave the loop */ break; } } /* If we made it here, it's because the Current memory * block not suitable; move to the next one */ current_location = current_location + current_location_mcb->size; } /* If we still don't have a valid location, we'll * have to ask the operating system for more memory */ if(! memory_location) { /* Move the program break numbytes further */ sbrk(numbytes); /* The new memory will be where the last valid * address left off */ memory_location = last_valid_address; /* We'll move the last valid address forward * numbytes */ last_valid_address = last_valid_address + numbytes; /* We need to initialize the mem_control_block */ current_location_mcb = memory_location; current_location_mcb->is_available = 0; current_location_mcb->size = numbytes; } /* Now, no matter what (well, except for error conditions), * memory_location has the address of the memory, including * the mem_control_block */ /* Move the pointer past the mem_control_block */ memory_location = memory_location + sizeof(struct mem_control_block); /* Return the pointer */ return memory_location; } 这就是我们的内存管理器。现在,我们只需要构建它,并在程序中使用它即可。 运行下面的命令来构建 malloc 兼容的分配程序(实际上,我们忽略了 realloc() 等一些函数,不过, malloc() 和 free() 才是最主要的函数): 清单 7. 编译分配程序 gcc -shared -fpic malloc.c -o malloc.so 该程序将生成一个名为 malloc.so 的文件,它是一个包含有我们的代码的共享库。 在 UNIX 系统中,现在您可以用您的分配程序来取代系统的 malloc(),做法如下: 清单 8. 替换您的标准的 malloc LD_PRELOAD=/path/to/malloc.so export LD_PRELOAD LD_PRELOAD 环境变量使动态链接器在加载任何可执行程序之前,先加载给定的共享库的符号。它还为特定库中的符号赋予优先权。因此,从现在起,该会话中的任何应用程序都将使用我们的 malloc(),而不是只有系统的应用程序能够使用。有一些应用程序不使用 malloc(),不过它们是例外。其他使用 realloc() 等其他内存管理函数的应用程序,或者错误地假定 malloc() 内部行为的那些应用程序,很可能会崩溃。ash shell 似乎可以使用我们的新 malloc() 很好地工作。 如果您想确保 malloc() 正在被使用,那么您应该通过向函数的入口点添加 write() 调用来进行测试。 我们的内存管理器在很多方面都还存在欠缺,但它可以有效地展示内存管理需要做什么事情。它的某些缺点包括: 由于它对系统中断点(一个全局变量)进行操作,所以它不能与其他分配程序或者 mmap 一起使用。 当分配内存时,在最坏的情形下,它将不得不遍历 全部进程内存;其中可能包括位于硬盘上的很多内存,这意味着操作系统将不得不花时间去向硬盘移入数据和从硬盘中移出数据。 没有很好的内存不足处理方案( malloc 只假定内存分配是成功的)。 它没有实现很多其他的内存函数,比如 realloc()。 由于 sbrk() 可能会交回比我们请求的更多的内存,所以在堆(heap)的末端会遗漏一些内存。 虽然 is_available 标记只包含一位信息,但它要使用完整的 4-字节 的字。 分配程序不是线程安全的。 分配程序不能将空闲空间拼合为更大的内存块。 分配程序的过于简单的匹配算法会导致产生很多潜在的内存碎片。 我确信还有很多其他问题。这就是为什么它只是一个例子! 其他 malloc 实现 malloc() 的实现有很多,这些实现各有优点与缺点。在设计一个分配程序时,要面临许多需要折衷的选择,其中包括: 分配的速度。 回收的速度。 有线程的环境的行为。 内存将要被用光时的行为。 局部缓存。 簿记(Bookkeeping)内存开销。 虚拟内存环境中的行为。 小的或者大的对象。 实时保证。 每一个实现都有其自身的优缺点集合。在我们的简单的分配程序中,分配非常慢,而回收非常快。另外,由于它在使用虚拟内存系统方面较差,所以它最适于处理大的对象。 还有其他许多分配程序可以使用。其中包括: Doug Lea Malloc:Doug Lea Malloc 实际上是完整的一组分配程序,其中包括 Doug Lea 的原始分配程序,GNU libc 分配程序和 ptmalloc。 Doug Lea 的分配程序有着与我们的版本非常类似的基本结构,但是它加入了索引,这使得搜索速度更快,并且可以将多个没有被使用的块组合为一个大的块。它还支持缓存,以便更快地再次使用最近释放的内存。 ptmalloc 是 Doug Lea Malloc 的一个扩展版本,支持多线程。在本文后面的 参考资料部分中,有一篇描述 Doug Lea 的 Malloc 实现的文章。 BSD Malloc:BSD Malloc 是随 4.2 BSD 发行的实现,包含在 FreeBSD 之中,这个分配程序可以从预先确实大小的对象构成的池中分配对象。它有一些用于对象大小的 size 类,这些对象的大小为 2 的若干次幂减去某一常数。所以,如果您请求给定大小的一个对象,它就简单地分配一个与之匹配的 size 类。这样就提供了一个快速的实现,但是可能会浪费内存。在 参考资料部分中,有一篇描述该实现的文章。 Hoard:编写 Hoard 的目标是使内存分配在多线程环境中进行得非常快。因此,它的构造以锁的使用为中心,从而使所有进程不必等待分配内存。它可以显著地加快那些进行很多分配和回收的多线程进程的速度。在 参考资料部分中,有一篇描述该实现的文章。 众多可用的分配程序中最有名的就是上述这些分配程序。如果您的程序有特别的分配需求,那么您可能更愿意编写一个定制的能匹配您的程序内存分配方式的分配程序。不过,如果不熟悉分配程序的设计,那么定制分配程序通常会带来比它们解决的问题更多的问题。要获得关于该主题的适当的介绍,请参阅 Donald Knuth 撰写的 The Art of Computer Programming Volume 1: Fundamental Algorithms 中的第 2.5 节“Dynamic Storage Allocation”(请参阅 参考资料中的链接)。它有点过时,因为它没有考虑虚拟内存环境,不过大部分算法都是基于前面给出的函数。 在 C++ 中,通过重载 operator new(),您可以以每个类或者每个模板为单位实现自己的分配程序。在 Andrei Alexandrescu 撰写的 Modern C++ Design 的第 4 章(“Small Object Allocation”)中,描述了一个小对象分配程序(请参阅 参考资料中的链接)。 基于 malloc() 的内存管理的缺点 不只是我们的内存管理器有缺点,基于 malloc() 的内存管理器仍然也有很多缺点,不管您使用的是哪个分配程序。对于那些需要保持长期存储的程序使用 malloc() 来管理内存可能会非常令人失望。如果您有大量的不固定的内存引用,经常难以知道它们何时被释放。生存期局限于当前函数的内存非常容易管理,但是对于生存期超出该范围的内存来说,管理内存则困难得多。而且,关于内存管理是由进行调用的程序还是由被调用的函数来负责这一问题,很多 API 都不是很明确。 因为管理内存的问题,很多程序倾向于使用它们自己的内存管理规则。C++ 的异常处理使得这项任务更成问题。有时好像致力于管理内存分配和清理的代码比实际完成计算任务的代码还要多!因此,我们将研究内存管理的其他选择。 回页首 半自动内存管理策略 引用计数 引用计数是一种 半自动(semi-automated)的内存管理技术,这表示它需要一些编程支持,但是它不需要您确切知道某一对象何时不再被使用。引用计数机制为您完成内存管理任务。 在引用计数中,所有共享的数据结构都有一个域来包含当前活动“引用”结构的次数。当向一个程序传递一个指向某个数据结构指针时,该程序会将引用计数增加 1。实质上,您是在告诉数据结构,它正在被存储在多少个位置上。然后,当您的进程完成对它的使用后,该程序就会将引用计数减少 1。结束这个动作之后,它还会检查计数是否已经减到零。如果是,那么它将释放内存。 这样做的好处是,您不必追踪程序中某个给定的数据结构可能会遵循的每一条路径。每次对其局部的引用,都将导致计数的适当增加或减少。这样可以防止在使用数据结构时释放该结构。不过,当您使用某个采用引用计数的数据结构时,您必须记得运行引用计数函数。另外,内置函数和第三方的库不会知道或者可以使用您的引用计数机制。引用计数也难以处理发生循环引用的数据结构。 要实现引用计数,您只需要两个函数 —— 一个增加引用计数,一个减少引用计数并当计数减少到零时释放内存。 一个示例引用计数函数集可能看起来如下所示: 清单 9. 基本的引用计数函数 /* Structure Definitions*/ /* Base structure that holds a refcount */ struct refcountedstruct { int refcount; } /* All refcounted structures must mirror struct * refcountedstruct for their first variables */ /* Refcount maintenance functions */ /* Increase reference count */ void REF(void *data) { struct refcountedstruct *rstruct; rstruct = (struct refcountedstruct *) data; rstruct->refcount++; } /* Decrease reference count */ void UNREF(void *data) { struct refcountedstruct *rstruct; rstruct = (struct refcountedstruct *) data; rstruct->refcount--; /* Free the structure if there are no more users */ if(rstruct->refcount == 0) { free(rstruct); } } REF 和 UNREF 可能会更复杂,这取决于您想要做的事情。例如,您可能想要为多线程程序增加锁,那么您可能想扩展 refcountedstruct,使它同样包含一个指向某个在释放内存之前要调用的函数的指针(类似于面向对象语言中的析构函数 —— 如果您的结构中包含这些指针,那么这是 必需的)。 当使用 REF 和 UNREF 时,您需要遵守这些指针的分配规则: UNREF 分配前左端指针(left-hand-side pointer)指向的值。 REF 分配后左端指针(left-hand-side pointer)指向的值。 在传递使用引用计数的结构的函数中,函数需要遵循以下这些规则: 在函数的起始处 REF 每一个指针。 在函数的结束处 UNREF 第一个指针。 以下是一个使用引用计数的生动的代码示例: 清单 10. 使用引用计数的示例 /* EXAMPLES OF USAGE */ /* Data type to be refcounted */ struct mydata { int refcount; /* same as refcountedstruct */ int datafield1; /* Fields specific to this struct */ int datafield2; /* other declarations would go here as appropriate */ }; /* Use the functions in code */ void dosomething(struct mydata *data) { REF(data); /* Process data */ /* when we are through */ UNREF(data); } struct mydata *globalvar1; /* Note that in this one, we don't decrease the * refcount since we are maintaining the reference * past the end of the function call through the * global variable */ void storesomething(struct mydata *data) { REF(data); /* passed as a parameter */ globalvar1 = data; REF(data); /* ref because of Assignment */ UNREF(data); /* Function finished */ } 由于引用计数是如此简单,大部分程序员都自已去实现它,而不是使用库。不过,它们依赖于 malloc 和 free 等低层的分配程序来实际地分配和释放它们的内存。 在 Perl 等高级语言中,进行内存管理时使用引用计数非常广泛。在这些语言中,引用计数由语言自动地处理,所以您根本不必担心它,除非要编写扩展模块。由于所有内容都必须进行引用计数,所以这会对速度产生一些影响,但它极大地提高了编程的安全性和方便性。以下是引用计数的益处: 实现简单。 易于使用。 由于引用是数据结构的一部分,所以它有一个好的缓存位置。 不过,它也有其不足之处: 要求您永远不要忘记调用引用计数函数。 无法释放作为循环数据结构的一部分的结构。 减缓几乎每一个指针的分配。 尽管所使用的对象采用了引用计数,但是当使用异常处理(比如 try 或 setjmp()/ longjmp())时,您必须采取其他方法。 需要额外的内存来处理引用。 引用计数占用了结构中的第一个位置,在大部分机器中最快可以访问到的就是这个位置。 在多线程环境中更慢也更难以使用。 C++ 可以通过使用 智能指针(smart pointers)来容忍程序员所犯的一些错误,智能指针可以为您处理引用计数等指针处理细节。不过,如果不得不使用任何先前的不能处理智能指针的代码(比如对 C 库的联接),实际上,使用它们的后果通实比不使用它们更为困难和复杂。因此,它通常只是有益于纯 C++ 项目。如果您想使用智能指针,那么您实在应该去阅读 Alexandrescu 撰写的 Modern C++ Design 一书中的“Smart Pointers”那一章。 内存池 内存池是另一种半自动内存管理方法。内存池帮助某些程序进行自动内存管理,这些程序会经历一些特定的阶段,而且每个阶段中都有分配给进程的特定阶段的内存。例如,很多网络服务器进程都会分配很多针对每个连接的内存 —— 内存的最大生存期限为当前连接的存在期。Apache 使用了池式内存(pooled memory),将其连接拆分为各个阶段,每个阶段都有自己的内存池。在结束每个阶段时,会一次释放所有内存。 在池式内存管理中,每次内存分配都会指定内存池,从中分配内存。每个内存池都有不同的生存期限。在 Apache 中,有一个持续时间为服务器存在期的内存池,还有一个持续时间为连接的存在期的内存池,以及一个持续时间为请求的存在期的池,另外还有其他一些内存池。因此,如果我的一系列函数不会生成比连接持续时间更长的数据,那么我就可以完全从连接池中分配内存,并知道在连接结束时,这些内存会被自动释放。另外,有一些实现允许注册 清除函数(cleanup functions),在清除内存池之前,恰好可以调用它,来完成在内存被清理前需要完成的其他所有任务(类似于面向对象中的析构函数)。 要在自己的程序中使用池,您既可以使用 GNU libc 的 obstack 实现,也可以使用 Apache 的 Apache Portable Runtime。GNU obstack 的好处在于,基于 GNU 的 Linux 发行版本中默认会包括它们。Apache Portable Runtime 的好处在于它有很多其他工具,可以处理编写多平台服务器软件所有方面的事情。要深入了解 GNU obstack 和 Apache 的池式内存实现,请参阅 参考资料部分中指向这些实现的文档的链接。 下面的假想代码列表展示了如何使用 obstack: 清单 11. obstack 的示例代码 #include #include /* Example code listing for using obstacks */ /* Used for obstack macros (xmalloc is a malloc function that exits if memory is exhausted */ #define obstack_chunk_alloc xmalloc #define obstack_chunk_free free /* Pools */ /* Only permanent allocations should go in this pool */ struct obstack *global_pool; /* This pool is for per-connection data */ struct obstack *connection_pool; /* This pool is for per-request data */ struct obstack *request_pool; void allocation_failed() { exit(1); } int main() { /* Initialize Pools */ global_pool = (struct obstack *) xmalloc (sizeof (struct obstack)); obstack_init(global_pool); connection_pool = (struct obstack *) xmalloc (sizeof (struct obstack)); obstack_init(connection_pool); request_pool = (struct obstack *) xmalloc (sizeof (struct obstack)); obstack_init(request_pool); /* Set the error handling function */ obstack_alloc_failed_handler = &allocation_failed; /* Server main loop */ while(1) { wait_for_connection(); /* We are in a connection */ while(more_requests_available()) { /* Handle request */ handle_request(); /* Free all of the memory allocated * in the request pool */ obstack_free(request_pool, NULL); } /* We're finished with the connection, time * to free that pool */ obstack_free(connection_pool, NULL); } } int handle_request() { /* Be sure that all object allocations are allocated * from the request pool */ int bytes_i_need = 400; void *data1 = obstack_alloc(request_pool, bytes_i_need); /* Do stuff to process the request */ /* return */ return 0; } 基本上,在操作的每一个主要阶段结束之后,这个阶段的 obstack 会被释放。不过,要注意的是,如果一个过程需要分配持续时间比当前阶段更长的内存,那么它也可以使用更长期限的 obstack,比如连接或者全局内存。传递给 obstack_free() 的 NULL 指出它应该释放 obstack 的全部内容。可以用其他的值,但是它们通常不怎么实用。 使用池式内存分配的益处如下所示: 应用程序可以简单地管理内存。 内存分配和回收更快,因为每次都是在一个池中完成的。分配可以在 O(1) 时间内完成,释放内存池所需时间也差不多(实际上是 O(n) 时间,不过在大部分情况下会除以一个大的因数,使其变成 O(1))。 可以预先分配错误处理池(Error-handling pools),以便程序在常规内存被耗尽时仍可以恢复。 有非常易于使用的标准实现。 池式内存的缺点是: 内存池只适用于操作可以分阶段的程序。 内存池通常不能与第三方库很好地合作。 如果程序的结构发生变化,则不得不修改内存池,这可能会导致内存管理系统的重新设计。 您必须记住需要从哪个池进行分配。另外,如果在这里出错,就很难捕获该内存池。 回页首 垃圾收集 垃圾收集(Garbage collection)是全自动地检测并移除不再使用的数据对象。垃圾收集器通常会在当可用内存减少到少于一个具体的阈值时运行。通常,它们以程序所知的可用的一组“基本”数据 —— 栈数据、全局变量、寄存器 —— 作为出发点。然后它们尝试去追踪通过这些数据连接到每一块数据。收集器找到的都是有用的数据;它没有找到的就是垃圾,可以被销毁并重新使用这些无用的数据。为了有效地管理内存,很多类型的垃圾收集器都需要知道数据结构内部指针的规划,所以,为了正确运行垃圾收集器,它们必须是语言本身的一部分。 收集器的类型 复制(copying): 这些收集器将内存存储器分为两部分,只允许数据驻留在其中一部分上。它们定时地从“基本”的元素开始将数据从一部分复制到另一部分。内存新近被占用的部分现在成为活动的,另一部分上的所有内容都认为是垃圾。另外,当进行这项复制操作时,所有指针都必须被更新为指向每个内存条目的新位置。因此,为使用这种垃圾收集方法,垃圾收集器必须与编程语言集成在一起。 标记并清理(Mark and sweep):每一块数据都被加上一个标签。不定期的,所有标签都被设置为 0,收集器从“基本”的元素开始遍历数据。当它遇到内存时,就将标签标记为 1。最后没有被标记为 1 的所有内容都认为是垃圾,以后分配内存时会重新使用它们。 增量的(Incremental):增量垃圾收集器不需要遍历全部数据对象。因为在收集期间的突然等待,也因为与访问所有当前数据相关的缓存问题(所有内容都不得不被页入(page-in)),遍历所有内存会引发问题。增量收集器避免了这些问题。 保守的(Conservative):保守的垃圾收集器在管理内存时不需要知道与数据结构相关的任何信息。它们只查看所有数据类型,并假定它们 可以全部都是指针。所以,如果一个字节序列可以是一个指向一块被分配的内存的指针,那么收集器就将其标记为正在被引用。有时没有被引用的内存会被收集,这样会引发问题,例如,如果一个整数域中包含一个值,该值是已分配内存的地址。不过,这种情况极少发生,而且它只会浪费少量内存。保守的收集器的优势是,它们可以与任何编程语言相集成。 Hans Boehm 的保守垃圾收集器是可用的最流行的垃圾收集器之一,因为它是免费的,而且既是保守的又是增量的,可以使用 --enable-redirect-malloc 选项来构建它,并且可以将它用作系统分配程序的简易替代者(drop-in replacement)(用 malloc/ free 代替它自己的 API)。实际上,如果这样做,您就可以使用与我们在示例分配程序中所使用的相同的 LD_PRELOAD 技巧,在系统上的几乎任何程序中启用垃圾收集。如果您怀疑某个程序正在泄漏内存,那么您可以使用这个垃圾收集器来控制进程。在早期,当 Mozilla 严重地泄漏内存时,很多人在其中使用了这项技术。这种垃圾收集器既可以在 Windows® 下运行,也可以在 UNIX 下运行。 垃圾收集的一些优点: 您永远不必担心内存的双重释放或者对象的生命周期。 使用某些收集器,您可以使用与常规分配相同的 API。 其缺点包括: 使用大部分收集器时,您都无法干涉何时释放内存。 在多数情况下,垃圾收集比其他形式的内存管理更慢。 垃圾收集错误引发的缺陷难于调试。 如果您忘记将不再使用的指针设置为 null,那么仍然会有内存泄漏。 回页首 结束语 一切都需要折衷:性能、易用、易于实现、支持线程的能力等,这里只列出了其中的一些。为了满足项目的要求,有很多内存管理模式可以供您使用。每种模式都有大量的实现,各有其优缺点。对很多项目来说,使用编程环境默认的技术就足够了,不过,当您的项目有特殊的需要时,了解可用的选择将会有帮助。下表对比了本文中涉及的内存管理策略。 表 1. 内存分配策略的对比 策略 分配速度 回收速度 局部缓存 易用性 通用性 实时可用 SMP 线程友好 定制分配程序 取决于实现 取决于实现 取决于实现 很难 无 取决于实现 取决于实现 简单分配程序 内存使用少时较快 很快 差 容易 高 否 否 GNU malloc 中 快 中 容易 高 否 中 Hoard 中 中 中 容易 高 否 是 引用计数 N/A N/A 非常好 中 中 是(取决于 malloc 实现) 取决于实现 池 中 非常快 极好 中 中 是(取决于 malloc 实现) 取决于实现 垃圾收集 中(进行收集时慢) 中 差 中 中 否 几乎不 增量垃圾收集 中 中 中 中 中 否 几乎不 增量保守垃圾收集 中 中 中 容易 高 否 几乎不 参考资料 您可以参阅本文在 developerWorks 全球站点上的 英文原文。 Web 上的文档 GNU C Library 手册的 obstacks 部分 提供了 obstacks 编程接口。 Apache Portable Runtime 文档 描述了它们的池式分配程序的接口。 基本的分配程序 Doug Lea 的 Malloc 是最流行的内存分配程序之一。 BSD Malloc 用于大部分基于 BSD 的系统中。 ptmalloc 起源于 Doug Lea 的 malloc,用于 GLIBC 之中。 Hoard 是一个为多线程应用程序优化的 malloc 实现。 GNU Memory-Mapped Malloc(GDB 的组成部分) 是一个基于 mmap() 的 malloc 实现。 池式分配程序 GNU Obstacks(GNU Libc 的组成部分)是安装最多的池式分配程序,因为在每一个基于 glibc 的系统中都有它。 Apache 的池式分配程序(Apache Portable Runtime 中) 是应用最为广泛的池式分配程序。 Squid 有其自己的池式分配程序。 NetBSD 也有其自己的池式分配程序。 talloc 是一个池式分配程序,是 Samba 的组成部分。 智能指针和定制分配程序 Loki C++ Library 有很多为 C++ 实现的通用模式,包括智能指针和一个定制的小对象分配程序。 垃圾收集器 Hahns Boehm Conservative Garbage Collector 是最流行的开源垃圾收集器,它可以用于常规的 C/C++ 程序。 关于现代操作系统中的虚拟内存的文章 Marshall Kirk McKusick 和 Michael J. Karels 合著的 A New Virtual Memory Implementation for Berkeley UNIX 讨论了 BSD 的 VM 系统。 Mel Gorman's Linux VM Documentation 讨论了 Linux VM 系统。 关于 malloc 的文章 Poul-Henning Kamp 撰写的 Malloc in Modern Virtual Memory Environments 讨论的是 malloc 以及它如何与 BSD 虚拟内存交互。 Berger、McKinley、Blumofe 和 Wilson 合著的 Hoard -- a Scalable Memory Allocator for Multithreaded Environments 讨论了 Hoard 分配程序的实现。 Marshall Kirk McKusick 和 Michael J. Karels 合著的 Design of a General Purpose Memory Allocator for the 4.3BSD UNIX Kernel 讨论了内核级的分配程序。 Doug Lea 撰写的 A Memory Allocator 给出了一个关于设计和实现分配程序的概述,其中包括设计选择与折衷。 Emery D. Berger 撰写的 Memory Management for High-Performance Applications 讨论的是定制内存管理以及它如何影响高性能应用程序。 关于定制分配程序的文章 Doug Lea 撰写的 Some Storage Management Techniques for Container Classes 描述的是为 C++ 类编写定制分配程序。 Berger、Zorn 和 McKinley 合著的 Composing High-Performance Memory Allocators 讨论了如何编写定制分配程序来加快具体工作的速度。 Berger、Zorn 和 McKinley 合著的 Reconsidering Custom Memory Allocation 再次提及了定制分配的主题,看是否真正值得为其费心。 关于垃圾收集的文章 Paul R. Wilson 撰写的 Uniprocessor Garbage Collection Techniques 给出了垃圾收集的一个基本概述。 Benjamin Zorn 撰写的 The Measured Cost of Garbage Collection 给出了关于垃圾收集和性能的硬数据(hard data)。 Hans-Juergen Boehm 撰写的 Memory Allocation Myths and Half-Truths 给出了关于垃圾收集的神话(myths)。 Hans-Juergen Boehm 撰写的 Space Efficient Conservative Garbage Collection 是一篇描述他的用于 C/C++ 的垃圾收集器的文章。 Web 上的通用参考资料 内存管理参考 中有很多关于内存管理参考资料和技术文章的链接。 关于内存管理和内存层级的 OOPS Group Papers 是非常好的一组关于此主题的技术文章。 C++ 中的内存管理讨论的是为 C++ 编写定制的分配程序。 Programming Alternatives: Memory Management 讨论了程序员进行内存管理时的一些选择。 垃圾收集 FAQ 讨论了关于垃圾收集您需要了解的所有内容。 Richard Jones 的 Garbage Collection Bibliography 有指向任何您想要的关于垃圾收集的文章的链接。 书籍 Michael Daconta 撰写的 C++ Pointers and Dynamic Memory Management 介绍了关于内存管理的很多技术。 Frantisek Franek 撰写的 Memory as a Programming Concept in C and C++ 讨论了有效使用内存的技术与工具,并给出了在计算机编程中应当引起注意的内存相关错误的角色。 Richard Jones 和 Rafael Lins 合著的 Garbage Collection: Algorithms for Automatic Dynamic Memory Management 描述了当前使用的最常见的垃圾收集算法。 在 Donald Knuth 撰写的 The Art of Computer Programming 第 1 卷 Fundamental Algorithms 的第 2.5 节“Dynamic Storage Allocation”中,描述了实现基本的分配程序的一些技术。 在 Donald Knuth 撰写的 The Art of Computer Programming 第 1 卷 Fundamental Algorithms 的第 2.3.5 节“Lists and Garbage Collection”中,讨论了用于列表的垃圾收集算法。 Andrei Alexandrescu 撰写的 Modern C++ Design 第 4 章“Small Object Allocation”描述了一个比 C++ 标准分配程序效率高得多的一个高速小对象分配程序。 Andrei Alexandrescu 撰写的 Modern C++ Design 第 7 章“Smart Pointers”描述了在 C++ 中智能指针的实现。 Jonathan 撰写的 Programming from the Ground Up 第 8 章“Intermediate Memory Topics”中有本文使用的简单分配程序的一个汇编语言版本。 来自 developerWorks 自我管理数据缓冲区内存 (developerWorks,2004 年 1 月)略述了一个用于管理内存的自管理的抽象数据缓存器的伪 C (pseudo-C)实现。 A framework for the user defined malloc replacement feature (developerWorks,2002 年 2 月)展示了如何利用 AIX 中的一个工具,使用自己设计的内存子系统取代原有的内存子系统。 掌握 Linux 调试技术 (developerWorks,2002 年 8 月)描述了可以使用调试方法的 4 种不同情形:段错误、内存溢出、内存泄漏和挂起。 在 处理 Java 程序中的内存漏洞 (developerWorks,2001 年 2 月)中,了解导致 Java 内存泄漏的原因,以及何时需要考虑它们。 在 developerWorks Linux 专区中,可以找到更多为 Linux 开发人员准备的参考资料。 从 developerWorks 的 Speed-start your Linux app 专区中,可以下载运行于 Linux 之上的 IBM 中间件产品的免费测试版本,其中包括 WebSphere® Studio Application Developer、WebSphere Application Server、DB2® Universal Database、Tivoli® Access Manager 和 Tivoli Directory Server,查找 how-to 文章和技术支持。 通过参与 developerWorks blogs 加入到 developerWorks 社区。 可以在 Developer Bookstore Linux 专栏中定购 打折出售的 Linux 书籍。 关于作者 Jonathan Bartlett 是 Programming from the Ground Up 一书的作者,这本书介绍的是 Linux 汇编语言编程。Jonathan Bartlett 是 New Media Worx 的总开发师,负责为客户开发 Web、视频、kiosk 和桌面应用程序。您可以通过 [email protected] 与 Jonathan 联系。
自制书签。因上传大小限制,分三卷压缩,分别是:《C#经典编程220例》.(明日科技).【带书签】.zip、《C#经典编程220例》.(明日科技).【带书签】.z01、《C#经典编程220例》.(明日科技).【带书签】.z02 第1章 c#语言及其开发环境 1 实例001 根据需要创建所需解决方案 2 实例002 统一窗体中控件的字体设置 3 实例003 设置程序代码行号 5 实例004 通过“格式”菜单布局窗体 6 实例005 为项目添加dll文件引用 7 实例006 为项目添加已有类 8 实例007 为项目添加第三方控件 9 实例008 为项目添加已有窗体 11 第2章 c#语言基础 12 实例009 利用for循环输出多行语句 13 实例010 输入并输出姓名 14 实例011 使用值类型和引用类型输出不同的字段 15 实例012 判断当前系统日期是星期几 17 实例013 定义局部变量输出不同的字段 19 实例014 定义循环内部变量并输出变量的值 20 实例015 定义常量计算圆的周长 21 实例016 使用“+”编写双重意义的表达式 22 实例017 使用小括号括起来的表达式 23 .实例018 使用算术运算符开发简单计算器 23 实例019 使用条件运算符判断指定年份是不是闰年 26 实例020 使用typeof关键字获取类的内部结构 28 实例021 巧用位移运算符获取汉字编码值 30 实例022 使用异或运算符对数字进行加密 31 第3章 流程控制语句 33 实例023 利用if…else语句判断分数是否及格 34 实例024 利用switch语句判断时间 35 实例025 利用while语句判断大小 36 实例026 打印九九乘法表 38 实例027 使用break语句提前退出循环语句 39 实例028 使用continue语句转移至循环开始处 40 实例029 使用流程控制语句报销业务花销 41 实例030 输出当前系统时间 42 实例031 利用for循环实现完数计算 44 实例032 开发一个猜数字游戏 45 第4章 字符与字符串 47 实例033 字母与ascii码的转换 48 实例034 获取字符串中汉字的个数 50 实例035 从字符串中分离文件路径、文件名及扩展名 51 实例036 对字符串进行加密与解密 53 实例037 开发一个进制转换器 56 实例038 将字符串的每个字符进行颠倒输出 60 实例039 根据标点符号对字符串进行分行 61 实例040 将汉字转换为拼音 63 实例041 商品金额的大小写转换 64 实例042 根据年份判断十二生肖 65 实例043 将字母全部转换为大写或小写 67 实例044 汉字与区位码的转换 69 第5章 数组和集合 71 实例045 在数组中添加一个元素 72 实例046 在数组中添加一个数组 73 实例047 计算两个矩形矩阵的乘积 75 实例048 获取多维数组的行数与列数 78 实例049 使用快速排序法对一维数组进行排序 79 实例050 使用sort方法对数组进行快速排序 81 实例051 按指定条件在数组中检索元素 82 实例052 反转数组中元素的顺序 84 实例053 使用希尔排序法对一维数组进行排序 85 实例054 向班级集合中添加学生信息 87 实例055 不改变长度删除数组中的元素 89 实例056 删除数组元素后改变其长度 90 第6章 c#面向对象程序设计 92 实例057 创建类的对象 93 实例058 使用构造函数 94 实例059 通过定义方法求一个数的平方 95 实例060 使用重载方法实现不同类型数据的计算 97 实例061 使用属性存储用户编号和姓名 99 实例062 使用索引器 101 实例063 通过类继承计算梯形面积 103 实例064 封装类实现一个简单的计算器 106 实例065 通过结构计算矩形的面积 107 实例066 通过结构计算圆形的面积 109 实例067 使用面向对象思想查找字符串中的所有数字 110 实例068 通过类的多态性确定人类的说话行为 112 第7章 异常处理和程序调试 114 实例069 使用try…catch语句捕获异常 115 实例070 根据实际年龄判断虚岁,使用try…catch捕获异常 116 实例071 调试程序执行时出现的结果 117 实例072 使用异常处理语句捕获异常 118 实例073 捕获连接数据库异常 120 第8章 windows窗体 122 实例074 简单的登录窗体 123 实例075 关闭窗体提示 124 实例076 创建简单的mdi应用程序 125 实例077 实现重复高效地利用基窗体 127 实例078 尝试制作一个半透明渐显窗体 128 实例079 使窗体标题栏文字右对齐 129 实例080 窗口间移动按钮 130 实例081 窗体中滚动的字幕 131 实例082 使用任意组件拖动窗体 133 实例083 窗体换肤程序 134 第9章 windows应用程序常用控件 137 实例084 在c#中设计出色彩斑斓的按钮 138 实例085 自动删除textbox控件中的非法字符 139 实例086 在richtextbox控件中替换文本文字 141 实例087 利用richtextbox控件实现文字定位与标示 142 实例088 将数据表中的字段添加到combobox控件中 143 实例089 对listbox控件中的数据进行排序 145 实例090 listbox控件拒绝添加重复信息 146 实例091 限制用户名称长度及设置密码文本 148 实例092 带查询功能的combobox控件 149 实例093 利用选择控件实现权限设置 150 实例094 利用richtextbox控件显示图文数据 153 实例095 在listbox控件间交换数据 155 第10章 windows应用程序高级控件 157 实例096 使用imagelist组件制作动画图片 158 实例097 在combobox下拉列表中显示图片 159 实例098 在listview控件中实现修改功能 161 实例099 将数据库数据添加到listview控件 162 实例100 在listview控件中绘制底纹 164 实例101 在treeview控件节点中显示图片 165 实例102 使用树型列表动态显示菜单 167 实例103 使用treeview控件遍历磁盘目录 169 实例104 使用errorprovider组件验证文本框输入 170 实例105 使用helpprovider组件调用帮助文件 172 实例106 使listview控件中的选择项高亮显示 173 实例107 在列表视图中拖动视图项 175 实例108 实现带复选框的treeview控件 177 实例109 将xml文件节点绑定到treeview控件中 178 实例110 修改treeview控件的节点文本 179 第11章 c#面向对象高级技术 181 实例111 利用接口实现选择不同的语言 182 实例112 使用接口作为方法参数进行编程 184 实例113 自定义抽象类计算圆形的面积 186 实例114 重写抽象方法实现多态性 188 实例115 使用密封类密封登录用户信息 191 实例116 使用密封类封装个人身份证信息 193 实例117 使用迭代器显示公交车站点 195 实例118 通过迭代器实现文字的动态效果 196 实例119 使用分部类制作一个计算器 199 实例120 使用分部类显示员工信息 201 实例121 使用泛型存储不同类型的数据列表 202 实例122 使用泛型去掉数组中的重复数字 204 实例123 通过重写虚方法实现加法运算 205 实例124 使用迭代器实现倒序遍历 207 实例125 通过泛型查找数组中的元素 209 第12章 ado.net数据访问技术 212 实例126 连接加密的access数据库 213 实例127 使用odbc dsn连接sql server数据库 214 实例128 使用ado.net对象录入数据 217 实例129 利用存储过程录入数据 219 实例130 使用oledbdatareader读取文本文件的内容 222 实例131 使用sqldatareader读取用户登录信息 223 实例132 使用断开式连接的方式录入数据 226 实例133 使用断开式连接批量更新数据库中的数据 227 实例134 使datagridview控件中被选定单元格的所在行变色 229 实例135 在datagridview控件中隔行换色 231 实例136 连接excel文件 232 实例137 读取和保存用户头像 233 实例138 判断是否重复输入数据 235 实例139 删除datagridview控件中的指定行 237 实例140 将access数据库导入excel文件中 238 第13章 水晶报表与打印 241 实例141 设计带有背景图的水晶报表 242 实例142 设置水晶报表的打印日期与时间 247 实例143 设置水晶报表中节的背景图片 249 实例144 打印窗体中的数据 250 实例145 打印商品入库单据 252 实例146 使用打印控件实现分页打印 255 实例147 动态绑定水晶报表 258 实例148 在水晶报表中使用公式字段 260 实例149 设计分组统计报表 261 实例150 打印一个空学生证 264 实例151 自定义横向或纵向打印 266 实例152 自定义打印页码范围 268 第14章 文件及数据流技术 271 实例153 根据日期动态建立文件 272 实例154 将长文件名转换成短文件名 273 实例155 获取所有逻辑磁盘目录 274 实例156 使用递归法删除文件夹中的所有文件 278 实例157 按行读取文本文件中的数据 279 实例158 使用缓冲流复制文件 280 实例159 文件批量更名 283 实例160 复制文件时显示复制进度 285 实例161 对指定文件夹中的文件进行分类存储 288 实例162 将文本文件转换成网页文件 290 实例163 伪装文件夹 292 实例164 word目录提取工具 296 第15章 gdi+绘图 302 实例165 在图像中实现自定义标记 303 实例166 辉光效果的文字 306 实例167 渐变效果的文字 310 实例168 绘制多边形 311 实例169 简单画图程序 313 实例170 仿qq截图功能 318 实例171 模拟石英钟 321 实例172 绘制贝塞尔曲线 323 实例173 绘制图形验证码 324 实例174 以椭圆形显示图像 326 实例175 使用双缓冲技术绘图 327 实例176 批量添加图片水印 330 第16章 线程的使用 336 实例177 使用线程读取数据库中的数据 337 实例178 使用线程制作qq农场小游戏 339 实例179 使用线程实现大容量数据的计算 342 实例180 使用多线程制作端口扫描工具 343 实例181 使用线程扫描局域网ip地址 345 实例182 使用线程休眠控制图片以百叶窗效果显示 347 实例183 使用线程控制向窗体中拖放图片并显示 349 实例184 使用线程制作动画效果的状态栏 351 实例185 使用线程遍历文件夹 352 实例186 使用线程实现从左向右以拉伸的方式显示图像 353 第17章 网络编程技术 357 实例187 通过ip地址获取主机名称 358 实例188 创建web页面浏览器 359 实例189 获取网络信息及流量 361 实例190 远程关闭与重启计算机 363 实例191 设计点对点聊天程序 366 实例192 电子邮件的发送与接收 368 实例193 获取网络中所有工作组名称 371 实例194 列出指定工作组中的所有计算机名 373 实例195 监测当前网络连接状态 375 实例196 使用udp协议设计聊天室 376 第18章 注册表技术 379 实例197 禁止运行注册表 380 实例198 使应用程序开机自动运行 381 实例199 获取本机安装的软件清单 384 实例200 禁止使用windows任务管理器 385 实例201 清除ie地址栏中的历史网址 387 实例202 将磁盘驱动器隐藏 388 实例203 禁止使用命令提示符 390 实例204 禁止修改ie浏览器主页 391 实例205 设置ie浏览器的默认主页 392 实例206 设置ie浏览器的默认下载路径 394 第19章 c#语言新技术 396 实例207 使用隐式类型局部变量实现字母的大小写转换 397 实例208 使用扩展方法显示员工信息 398 实例209 使用lambda表达式查找指定字符串 400 实例210 检查序列中是否包含指定元素 402 实例211 使用linq在一个循环中遍历多个数组 403 实例212 过滤文章中包含指定单词的句子 404 实例213 使用linq生成随机序列 406 实例214 筛选指定类型的元素 408 实例215 使用linq技术对对象进行筛选操作 409 实例216 使用linq技术对对象进行排序操作 410 实例217 使用linq技术对对象进行聚合操作 413 实例218 使用linq技术对对象进行联接操作 415 第20章 windows应用程序打包部署 417 实例219 打包程序时设置桌面图标 418 实例220 打包.net framework 4.0框架 420
比较详实 第三章: C语言程序设计初步 C语言程序设计 本课介绍C语言程序设计的基本方法和基本的程序语句。 从程序流程的角度来看,程序可以分为三种基本结构, 即顺序结构、分支结构循环结构。 这三种基本结构可以组成所有的各种复杂程序。C语言提供了多种语句来实现这些程序结构。 本章介绍这些基本语句及其应用,使读者对C程序有一个初步的认识, 为后面各章的学习打下基础。 C程序的语句 C程序的执行部分是由语句组成的。 程序的功能也是由执行语句实现的。 C语句可分为以下五类: 1.表达式语句 2.函数调用语句 3.控制语句 4.复合语句 5.空语句 1.表达式语句 表达式语句由表达式加上分号“;”组成。其一般形式为: 表达式; 执行表达式语句就是计算表达式的值。例如: x=y+z; 赋值语句y+z; 加法运算语句,但计算结果不能保留,无实际意义i++; 自增1语句,i值增1 2.函数调用语句 由函数名、实际参数加上分号“;”组成。其一般形式为: 函数名(实际参数表); 执行函数语句就是调用函数体并把实际参数赋予函数定义中的形式参数,然后执行被调函数体中的语句,求取函数值。(在第五章函数中再详细介绍)例如printf("C Program");调用库函数,输出字符串。 3.控制语句 控制语句用于控制程序的流程, 以实现程序的各种结构方式。 它们由特定的语句定义符组成。C语言有九种控制语句。 可分成以下三类: (1) 条件判断语句   if语句,switch语句 (2) 循环执行语句   do while语句,while语句,for语句 (3) 转向语句   break语句,goto语句,continue语句,return语句 4.复合语句 把多个语句用括号{}括起来组成的一个语句称复合语句。 在程序中应把复合语句看成是单条语句,而不是多条语句,例如 { x=y+z; a=b+c; printf(“%d%d”,x,a); } 是一条复合语句。复合语句内的各条语句都必须以分号“;”结尾,在括号“}”外不能加分号。 5.空语句 只有分号“;”组成的语句称为空语句。 空语句是什么也不执行的语句。在程序中空语句可用来作空循环体。例如 while(getchar()!='\n'); 本语句的功能是,只要从键盘输入的字符不是回车则重新输入。这里的循环体为空语句。 赋值语句 赋值语句是由赋值表达式再加上分号构成的表达式语句。 其一般形式为: 变量=表达式; 赋值语句的功能和特点都与赋值表达式相同。 它是程序中使用最多的语句之一。 在赋值语句的使用中需要注意以下几点: 1.由于在赋值符“=”右边的表达式也可以又是一个赋值表达式,因此,下述形式 变量=(变量=表达式); 是成立的,从而形成嵌套的情形。其展开之后的一般形式为: 变量=变量=…=表达式; 例如: a=b=c=d=e=5;按照赋值运算符的右接合性,因此实际上等效于: e=5; d=e; c=d; b=c; a=b; 2.注意在变量说明中给变量赋初值和赋值语句的区别。给变量赋初值是变量说明的一部分,赋初值后的变量与其后的其它同类变量之间仍必须用逗号间隔,而赋值语句则必须用分号结尾。 3.在变量说明中,不允许连续给多个变量赋初值。 如下述说明是错误的: int a=b=c=5 必须写为 int a=5,b=5,c=5; 而赋值语句允许连续赋值 4.注意赋值表达式和赋值语句的区别。赋值表达式是一种表达式,它可以出现在任何允许表达式出现的地方,而赋值语句则不能。 下述语句是合法的: if((x=y+5)>0) z=x; 语句的功能是,若表达式x=y+5大于0则z=x。下述语句是非法的: if((x=y+5;)>0) z=x; 因为=y+5;是语句,不能出现在表达式中。 数据输出语句 本小节介绍的是向标准输出设备显示器输出数据的语句。在C语言中,所有的数据输入/输出都是由库函数完成的。 因此都是函数语句。本小节先介绍printf函数和putchar函数。printf函数printf函数称为格式输出函数,其关键字最末一个字母f即为“格式”(format)之意。其功能是按用户指定的格式, 把指定的数据显示到显示器屏幕上。在前面的例题中我们已多次使用过这个函数。 一、printf函数调用的一般形式 printf函数是一个标准库函数,它的函数原型在头文件“stdio.h”中。但作为一个特例,不要求在使用 printf 函数之前必须包含stdio.h文件。printf函数调用的一般形式为: printf(“格式控制字符串”,输出表列)其中格式控制字符串用于指定输出格式。 格式控制串可由格式字符串和非格式字符串两种组成。格式字符串是以%开头的字符串,在%后面跟有各种格式字符,以说明输出数据的类型、形式、长度、小数位数等。如“%d”表示按十进制整型输出,“%ld”表示按十进制长整型输出,“%c”表示按字符型输出等。后面将专门给予讨论。 非格式字符串在输出时原样照印,在显示中起提示作用。 输出表列中给出了各个输出项, 要求格式字符串和各输出项在数量和类型上应该一一对应。 void main() { int a=88,b=89; printf("%d %d\n",a,b); printf("%d,%d\n",a,b); printf("%c,%c\n",a,b); printf("a=%d,b=%d",a,b); } a<--8,b<--89 printf("%d %d\n",a,b); printf("%d,%d\n",a,b); printf("%c,%c\n",a,b); printf("a=%d,b=%d",a,b); 本例中四次输出了a,b的值,但由于格式控制串不同,输出的结果也不相同。第四行的输出语句格式控制串中,两格式串%d 之间加了一个空格(非格式字符),所以输出的a,b值之间有一个空格。第五行的printf语句格式控制串中加入的是非格式字符逗号, 因此输出的a,b值之间加了一个逗号。第六行的格式串要求按字符型输出 a,b值。第七行中为了提示输出结果又增加了非格式字符串。 二、格式字符串 在Turbo C中格式字符串的一般形式为: [标志][输出最小宽度][.精度][长度]类型 其中方括号[]中的项为可选项。各项的意义介绍如下: 1.类型类型字符用以表示输出数据的类型,其格式符和意义下表所示: 表示输出类型的格式字符       格式字符意义 d                 以十进制形式输出带符号整数(正数不输出符号) o                 以八进制形式输出无符号整数(不输出前缀O) x                 以十六进制形式输出无符号整数(不输出前缀OX) u                 以十进制形式输出无符号整数 f                 以小数形式输出单、双精度实数 e                 以指数形式输出单、双精度实数 g                 以%f%e中较短的输出宽度输出单、双精度实数 c                 输出单个字符 s                 输出字符串 2.标志 标志字符为-、+、#、空格四种,其意义下表所示: 标志格式字符      标 志 意 义 -          结果左对齐,右边填空格 +          输出符号(正号或负号)空格输出值为正时冠以空格,为负时冠以负号 #          对c,s,d,u类无影响;对o类, 在输出时加前 缀o         对x类,在输出时加前缀0x;对e,g,f 类当结果有小数时才给出小数点 3.输出最小宽度 用十进制整数来表示输出的最少位数。 若实际位数多于定义的宽度,则按实际位数输出, 若实际位数少于定义的宽度则补以空格或0。 4.精度 精度格式符以“.”开头,后跟十进制整数。本项的意义是:如果输出数字,则表示小数的位数;如果输出的是字符, 则表示输出字符的个数;若实际位数大于所定义的精度数,则截去超过的部分。 5.长度 长度格式符为h,l两种,h表示按短整型量输出,l表示按长整型量输出。 void main(){ int a=15; float b=138.3576278; double c=35648256.3645687; char d='p'; printf("a=%d,%5d,%o,%x\n",a,a,a,a); printf("b=%f,%lf,%5.4lf,%e\n",b,b,b,b); printf("c=%lf,%f,%8.4lf\n",c,c,c); printf("d=%c,%8c\n",d,d); } a<--15 b<--138.3576278 c<--35648256.3645687 d<--'p' main() { int a=29; float b=1243.2341; double c=24212345.24232; char d='h'; printf("a=%d,%5d,%o,%x\n",a,a,a,a); printf("b=%f,%lf,%5.4lf,%e\n",b,b,b,b); printf("c=%lf,%f,%8.4lf\n",c,c,c); printf("d=%c,%8c\n",d,d); } 本例第七行中以四种格式输出整型变量a的值,其中“%5d ”要求输出宽度为5,而a值为15只有两位故补三个空格。 第八行中以四种格式输出实型量b的值。其中“%f”和“%lf ”格式的输出相同,说明“l”符对“f”类型无影响。“%5.4lf”指定输出宽度为5,精度为4,由于实际长度超过5故应该按实际位数输出,小数位数超过4位部分被截去。第九行输出双精度实数,“%8.4lf ”由于指定精度为4位故截去了超过4位的部分。第十行输出字符量d,其中“%bc ”指定输出宽度为8故在输出字符p之前补加7个空格。 使用printf函数时还要注意一个问题, 那就是输出表列中的求值顺序。不同的编译系统不一定相同,可以从左到右, 也可从右到左。Turbo C是按从右到左进行的。如把例2.13改写如下述形式: void main(){ int i=8; printf("%d\n%d\n%d\n%d\n%d\n%d\n",++i,--i,i--,i++,-i--); } i<--8 这个程序与例2.13相比只是把多个printf语句改一个printf 语句输出。但从结果可以看出是不同的。为什么结果会不同呢?就是因为printf函数对输出表中各量求值的顺序是自右至左进行 的。在式中,先对最后一项“-i--”求值,结果为-8,然后i自减1后为7。 再对“-i++”项求值得-7,然后i自增1后为8。再对“i--”项求值得8,然后i再自减1后为7。再求“i++”项得7,然后I再自增1后为8。 再求“--i”项,i先自减1后输出,输出值为7。 最后才求输出表列中的第一项“++i”,此时i自增1后输出8。但是必须注意, 求值顺序虽是自右至左,但是输出顺序还是从左至右, 因此得到的结果是上述输出结果。 字符输出函数 putchar 函数 putchar 函数是字符输出函数, 其功能是在显示器上输出单个字符。其一般形式为: putchar(字符变量) 例如: putchar('A'); 输出大写字母A putchar(x); 输出字符变量x的值 putchar('\n'); 换行 对控制字符则执行控制功能,不在屏幕上显示。 使用本函数前必须要用文件包含命令: #include<stdio.h> void main(){ char a='B',b='o',c='k'; putchar(a);putchar(b);putchar(b);putchar(c);putchar('\t'); putchar(a);putchar(b); putchar('\n'); putchar(b);putchar(c); } 数据输入语句 C语言的数据输入也是由函数语句完成的。 本节介绍从标准输入设备—键盘上输入数据的函数scanf和getchar。 scanf函数 scanf函数称为格式输入函数,即按用户指定的格式从键盘上把数据输入到指定的变量之中。 一、scanf函数的一般形式 scanf函数是一个标准库函数,它的函数原型在头文件“stdio.h”中,与printf函数相同,C语言也允许在使用scanf函数之前不必包含stdio.h文件。scanf函数的一般形式为: scanf(“格式控制字符串”,地址表列); 其中,格式控制字符串的作用与printf函数相同,但不能显示非格式字符串, 也就是不能显示提示字符串。地址表列中给出各变量的地址。 地址是由地址运算符“&”后跟变量名组成的。例如,&a,&b分别表示变量a和变量b 的地址。这个地址就是编译系统在内存中给a,b变量分配的地址。在C语言中,使用了地址这个概念,这是与其它语言不同的。 应该把变量的值和变量的地址这两个不同的概念区别开来。变量的地址是C编译系统分配的,用户不必关心具体的地址是多少。 变量的地址和变量值的关系如下: &a--->a567 a为变量名,567是变量的值,&a是变量a的地址。在赋值表达式中给变量赋值,如: a=567 在赋值号左边是变量名,不能写地址,而scanf函数在本质上也是给变量赋值,但要求写变量的地址,如&a。 这两者在形式上是不同的。&是一个取地址运算符,&a是一个表达式,其功能是求变量的地址。 void main(){ int a,b,c; printf("input a,b,c\n"); scanf("%d%d%d",&a,&b,&c); printf("a=%d,b=%d,c=%d",a,b,c); } 注意&的用法! 在本例中,由于scanf函数本身不能显示提示串,故先用printf语句在屏幕上输出提示,请用户输入a、b、c的值。执行scanf语句,则退出TC屏幕进入用户屏幕等待用户输入。用户输入7、8、9后按下回车键,此时,系统又将返回TC屏幕。在scanf语句的格式串中由于没有非格式字符在“%d%d%d”之间作输入时的间隔, 因此在输入时要用一个以上的空格或回车键作为每两个输入数之间的间隔。 如: 7 8 9 或 7 8 9 格式字符串 格式字符串的一般形式为: %[*][输入数据宽度][长度]类型 其中有方括号[]的项为任选项。各项的意义如下: 1.类型 表示输入数据的类型,其格式符和意义下表所示。 格式    字符意义 d     输入十进制整数 o     输入八进制整数 x     输入十六进制整数 u     输入无符号十进制整数 f或e    输入实型数(用小数形式或指数形式) c     输入单个字符 s     输入字符串 2.“*”符 用以表示该输入项读入后不赋予相应的变量,即跳过该输入值。 如 scanf("%d %*d %d",&a,&b);当输入为:1 2 3 时,把1赋予a,2被跳过,3赋予b。 3.宽度 用十进制整数指定输入的宽度(即字符数)。例如: scanf("%5d",&a); 输入: 12345678 只把12345赋予变量a,其余部分被截去。又如: scanf("%4d%4d",&a,&b); 输入: 12345678将把1234赋予a,而把5678赋予b。 4.长度 长度格式符为l和h,l表示输入长整型数据(如%ld) 和双精度浮点数(如%lf)。h表示输入短整型数据。 使用scanf函数还必须注意以下几点: a. scanf函数中没有精度控制,如: scanf("%5.2f",&a); 是非法的。不能企图用此语句输入小数为2位的实数。 b. scanf中要求给出变量地址,如给出变量名则会出错。如 scanf("%d",a);是非法的,应改为scnaf("%d",&a);才是合法的。 c. 在输入多个数值数据时,若格式控制串中没有非格式字符作输入数据之间的间隔则可用空格,TAB或回车作间隔。C编译在碰到空格,TAB,回车或非法数据(如对“%d”输入“12A”时,A即为非法数据)时即认为该数据结束。 d. 在输入字符数据时,若格式控制串中无非格式字符,则认为所有输入的字符均为有效字符。例如: scanf("%c%c%c",&a,&b,&c); 输入为: d e f 则把'd'赋予a, 'f'赋予b,'e'赋予c。只有当输入为: def 时,才能把'd'赋于a,'e'赋予b,'f'赋予c。 如果在格式控制中加入空格作为间隔,如 scanf ("%c %c %c",&a,&b,&c);则输入时各数据之间可加空格。 void main(){ char a,b; printf("input character a,b\n"); scanf("%c%c",&a,&b); printf("%c%c\n",a,b); } scanf("'C14F14%c%c",&a,&b); printf("%c%c\n",a,b); 由于scanf函数"%c%c"中没有空格,输入M N,结果输出只有M。 而输入改为MN时则可输出MN两字符,见下面的输入运行情况: input character a,b MN MN void main(){ char a,b; printf("input character a,b\n"); scanf("%c %c",&a,&b); printf("\n%c%c\n",a,b); } scanf("%c %c",&a,&b); 本例表示scanf格式控制串"%c %c"之间有空格时, 输入的数据之间可以有空格间隔。e. 如果格式控制串中有非格式字符则输入时也要输入该非格式字符。 例如: scanf("%d,%d,%d",&a,&b,&c); 其中用非格式符“ , ”作间隔符,故输入时应为: 5,6,7 又如: scanf("a=%d,b=%d,c=%d",&a,&b,&c); 则输入应为 a=5,b=6,c=7g. 如输入的数据与输出的类型不一致时,虽然编译能够通过,但结果将不正确。 void main(){ int a; printf("input a number\n"); scanf("%d",&a); printf("%ld",a); } 由于输入数据类型为整型, 而输出语句的格式串中说明为长整型,因此输出结果和输入数据不符。如改动程序如下: void main(){ long a; printf("input a long integer\n"); scanf("%ld",&a); printf("%ld",a); } 运行结果为: input a long integer 1234567890 1234567890 当输入数据改为长整型后,输入输出数据相等。 键盘输入函数 getchar函数getchar函数的功能是从键盘上输入一个字符。其一般形式为: getchar(); 通常把输入的字符赋予一个字符变量,构成赋值语句,如: char c; c=getchar(); #include<stdio.h> void main(){ char c; printf("input a character\n"); c=getchar(); putchar(c); } 使用getchar函数还应注意几个问题: 1.getchar函数只能接受单个字符,输入数字也按字符处理。输入多于一个字符时,只接收第一个字符。 2.使用本函数前必须包含文件“stdio.h”。 3.在TC屏幕下运行含本函数程序时,将退出TC 屏幕进入用户屏幕等待用户输入。输入完毕再返回TC屏幕。 void main(){ char a,b,c; printf("input character a,b,c\n"); scanf("%c %c %c",&a,&b,&c); printf("%d,%d,%d\n%c,%c,%c\n",a,b,c,a-32,b-32,c-32); } 输入三个小写字母 输出其ASCII码和对应的大写字母。 void main(){ int a; long b; float f; double d; char c; printf("%d,%d,%d,%d,%d",sizeof(a),sizeof(b),sizeof(f) ,sizeof(d),sizeof(c)); } 输出各种数据类型的字节长度。 分支结构程序 关系运算符和表达式 在程序中经常需要比较两个量的大小关系, 以决定程序下一步的工作。比较两个量的运算符称为关系运算符。 在C语言中有以下关系运算符: < 小于 <= 小于或等于 > 大于 >= 大于或等于 == 等于 != 不等于 关系运算符都是双目运算符,其结合性均为左结合。 关系运算符的优先级低于算术运算符,高于赋值运算符。 在六个关系运算符中,<,<=,>,>=的优先级相同,高于==和!=,==和!=的优先级相同。 关系表达式 关系表达式的一般形式为: 表达式 关系运算符 表达式 例如:a+b>c-d,x>3/2,'a'+1<c,-i-5*j==k+1;都是合法的关系表达式。由于表达式也可以又是关系表达式。 因此也允许出现嵌套的情况,例如:a>(b>c),a!=(c==d)等。关系表达式的值是“真”和“假”,用“1”和“0”表示。 如: 5>0的值为“真”,即为1。(a=3)>(b=5)由于3>5不成立,故其值为假,即为0。 void main(){ char c='k'; int i=1,j=2,k=3; float x=3e+5,y=0.85; printf("%d,%d\n",'a'+5<c,-i-2*j>=k+1); printf("%d,%d\n",1<j<5,x-5.25<=x+y); printf("%d,%d\n",i+j+k==-2*j,k==j==i+5); } char c='k'; int i=1,j=2,k=3; float x=3e+5,y=0.85; printf("%d,%d\n",'a'+5<c,-i-2*j>=k+1); printf("%d,%d\n",1<j<5,x-5.25<=x+y); printf("%d,%d\n",i+j+k==-2*j,k==j==i+5); 在本例中求出了各种关系运算符的值。 字符变量是以它对应的ASCII码参与运算的。对于含多个关系运算符的表达式,如k==j==i+5,根据运算符的左结合性,先计算k==j,该式不成立,其值为0,再计算0==i+5,也不成立,故表达式值为0。 逻辑运算符和表达式 逻辑运算符C语言中提供了三种逻辑运算符 && 与运算 || 或运算 ! 非运算 与运算符&&和或运算符||均为双目运算符。具有左结合性。 非 运算符!为单目运算符,具有右结合性。逻辑运算符和其它运算符优先级的关系可表示如下: 按照运算符的优先顺序可以得出: a>b && c>d等价于(a>b) && (c>d) !b==c||d<a等价于((!b)==c)||(d<a) a+b>c && x+y<b等价于((a+b)>c) && ((x+y)<b) 逻辑运算的值 逻辑运算的值也为“真”和“假”两种,用“1”和“0 ”来表示。其求值规则如下: 1.与运算&&参与运算的两个量都为真时,结果才为真,否则为假。例如,5>0 && 4>2,由于5>0为真,4>2也为真,相与的结果也为真。 2.或运算||参与运算的两个量只要有一个为真,结果就为真。 两个量都为假时,结果为假。例如:5>0||5>8,由于5>0为真,相或的结果也就为真 3.非运算!参与运算量为真时,结果为假;参与运算量为假时,结果为真。 例如:!(5>0)的结果为假。 虽然C编译在给出逻辑运算值时,以“1”代表“真”,“0 ”代表“假”。 但反过来在判断一个量是为“真”还是为“假”时,以“0”代表“假”,以非“0”的数值作为“真”。例如:由于5和3均为非“0”因此5&&3的值为“真”,即为1。 又如:5||0的值为“真”,即为1。 逻辑表达式逻辑表达式的一般形式为: 表达式 逻辑运算符 表达式 其中的表达式可以又是逻辑表达式,从而组成了嵌套的情形。例如:(a&&b)&&c根据逻辑运算符的左结合性,上式也可写为: a&&b&&c 逻辑表达式的值是式中各种逻辑运算的最后值,以“1”和“0”分别代表“真”和“假”。 void main(){ char c='k'; int i=1,j=2,k=3; float x=3e+5,y=0.85; printf("%d,%d\n",!x*!y,!!!x); printf("%d,%d\n",x||i&&j-3,i<j&&x<y); printf("%d,%d\n",i==5&&c&&(j=8),x+y||i+j+k); } 本例中!x和!y分别为0,!x*!y也为0,故其输出值为0。由于x为非0,故!!!x的逻辑值为0。对x|| i && j-3式,先计算j-3的值为非0,再求i && j-3的逻辑值为1,故x||i&&j-3的逻辑值为 1。对i<j&&x<y式,由于i<j的值为1,而x<y为0故表达式的值为1,0相与,最后为0,对i==5&&c&&(j=8)式,由于i==5为假,即值为0, 该表达式由两个与运算组成,所以整个表达式的值为0。对于式x+ y||i+j+k 由于x+y的值为非0,故整个或表达式的值为1。 if语句 用if语句可以构成分支结构。它根据给定的条件进行判断, 以决定执行某个分支程序段。C语言的if语句有三种基本形式。 1.第一种形式为基本形式 if(表达式) 语句; 其语义是:如果表达式的值为真,则执行其后的语句, 否则不执行该语句。其过程可表示为下图 void main(){ int a,b,max; printf("\n input two numbers: "); scanf("%d%d",&a,&b); max=a; if (max<b) max=b; printf("max=%d",max); } 输入两个整数,输出其中的大数。 scanf("%d%d",&a,&b); max=a; if (max<b) max=b; printf("max=%d",max); 本例程序中,输入两个数a,b。把a先赋予变量max,再用if语句判别max和b的大小,如max小于b,则把b赋予max。因此max中总是大数,最后输出max的值。 2.第二种形式为if-else形式 if(表达式) 语句1; else 语句2; 其语义是:如果表达式的值为真,则执行语句1,否则执行语句2 。 void main(){ int a, b; printf("input two numbers: "); scanf("%d%d",&a,&b); if(a>b) printf("max=%d\n",a); else printf("max=%d\n",b); } 输入两个整数,输出其中的大数。改用if-else语句判别a,b的大小,若a大,则输出a,否则输出b。 3.第三种形式为if-else-if形式 前二种形式的if语句一般都用于两个分支的情况。 当有多个分支选择时,可采用if-else-if语句,其一般形式为: if(表达式1) 语句1; else if(表达式2) 语句2; else if(表达式3) 语句3; … else if(表达式m) 语句m; else 语句n; 其语义是:依次判断表达式的值,当出现某个值为真时, 则执行其对应的语句。然后跳到整个if语句之外继续执行程序。 如果所有的表达式均为假,则执行语句n 。 然后继续执行后续程序。 if-else-if语句的执行过程如图3—3所示。 #include"stdio.h" void main(){ char c; printf("input a character: "); c=getchar(); if(c<32) printf("This is a control character\n"); else if(c>='0'&&c<='9') printf("This is a digit\n"); else if(c>='A'&&c<='Z') printf("This is a capital letter\n"); else if(c>='a'&&c<='z') printf("This is a small letter\n"); else printf("This is an other character\n"); } 本例要求判别键盘输入字符的类别。可以根据输入字符的ASCII码来判别类型。由ASCII码表可知ASCII值小于32的为控制字符。 在“0”和“9”之间的为数字,在“A”和“Z”之间为大写字母, 在“a”和“z”之间为小写字母,其余则为其它字符。 这是一个多分支选择的问题,用if-else-if语句编程,判断输入字符ASCII码所在的范围,分别给出不同的输出。例如输入为“g”,输出显示它为小写字符。 4.在使用if语句中还应注意以下问题 (1) 在三种形式的if语句中,在if关键字之后均为表达式。 该表达式通常是逻辑表达式或关系表达式, 但也可以是其它表达式,如赋值表达式等,甚至也可以是一个变量。例如: if(a=5) 语句;if(b) 语句; 都是允许的。只要表达式的值为非0,即为“真”。如在if(a=5)…;中表达式的值永远为非0,所以其后的语句总是要执行的,当然这种情况在程序中不一定会出现,但在语法上是合法的。 又如,有程序段: if(a=b) printf("%d",a); else printf("a=0"); 本语句的语义是,把b值赋予a,如为非0则输出该值,否则输出“a=0”字符串。这种用法在程序中是经常出现的。 (2) 在if语句中,条件判断表达式必须用括号括起来, 在语句之后必须加分号。 (3) 在if语句的三种形式中,所有的语句应为单个语句,如果要想在满足条件时执行一组(多个)语句,则必须把这一组语句用{} 括起来组成一个复合语句。但要注意的是在}之后不能再加分号。 例如: if(a>b){ a++; b++; } else{ a=0; b=10; } if语句的嵌套 当if语句中的执行语句又是if语句时,则构成了if 语句嵌套的情形。其一般形式可表示如下: if(表达式) if语句; 或者为 if(表达式) if语句; else if语句; 在嵌套内的if语句可能又是if-else型的,这将会出现多个if和多个else重叠的情况,这时要特别注意if和else的配对问题。例如: if(表达式1) if(表达式2) 语句1; else 语句2; 其中的else究竟是与哪一个if配对呢? 应该理解为:   还是应理解为: if(表达式1)    if(表达式1)  if(表达式2)     if(表达式2)   语句1;       语句1; else         else   语句2;       语句2; 为了避免这种二义性,C语言规定,else 总是与它前面最近的if配对,因此对上述例子应按前一种情况理解。 比较两个数的大小关系。 void main(){ int a,b; printf("please input A,B: "); scanf("%d%d",&a,&b); if(a!=b) if(a>b) printf("A>B\n"); else printf("A<B\n"); else printf("A=B\n"); } 本例中用了if语句的嵌套结构。 采用嵌套结构实质上是为了进行多分支选择,例3.16实际上有三种选择即A>B、A<B或A=B。这种问题用if-else-if语句也可以完成。而且程序更加清晰。因此, 在一般情况下较少使用if语句的嵌套结构。 以使程序更便于阅读理解。 void main(){ int a,b; printf("please input A,B: "); scanf("%d%d",&a,&b); if(a==b) printf("A=B\n"); else if(a>b) printf("A>B\n"); else printf("A<B\n"); } 条件运算符和条件表达式 如果在条件语句中,只执行单个的赋值语句时, 常可使用条件表达式来实现。不但使程序简洁,也提高了运行效率。 条件运算符为?和:,它是一个三目运算符,即有三个参与运算的量。由条件运算符组成条件表达式的一般形式为: 表达式1? 表达式2: 表达式3 其求值规则为:如果表达式1的值为真,则以表达式2 的值作为条件表达式的值,否则以表达式2的值作为整个条件表达式的值。 条件表达式通常用于赋值语句之中。 例如条件语句: if(a>b) max=a; else max=b; 可用条件表达式写为 max=(a>b)?a:b; 执行该语句的语义是:如a>b为真,则把a赋予max,否则把b 赋予max。 使用条件表达式时,还应注意以下几点: 1. 条件运算符的运算优先级低于关系运算符和算术运算符,但高于赋值符。因此 max=(a>b)?a:b可以去掉括号而写为 max=a>b?a:b 2. 条件运算符?和:是一对运算符,不能分开单独使用。 3. 条件运算符的结合方向是自右至左。 例如: a>b?a:c>d?c:d应理解为 a>b?a:(c>d?c:d) 这也就是条件表达式嵌套的情形,即其中的表达式3又是一个条 件表达式。 void main(){ int a,b,max; printf("\n input two numbers: "); scanf("%d%d",&a,&b); printf("max=%d",a>b?a:b); } 用条件表达式对上例重新编程,输出两个数中的大数。 switch语句 C语言还提供了另一种用于多分支选择的switch语句, 其一般形式为: switch(表达式){ case常量表达式1: 语句1; case常量表达式2: 语句2; … case常量表达式n: 语句n; default : 语句n+1; } 其语义是:计算表达式的值。 并逐个与其后的常量表达式值相比较,当表达式的值与某个常量表达式的值相等时, 即执行其后的语句,然后不再进行判断,继续执行后面所有case后的语句。 如表达式的值与所有case后的常量表达式均不相同时,则执行default后的语句。 void main(){ int a; printf("input integer number: "); scanf("%d",&a); switch (a){ case 1:printf("Monday\n"); case 2:printf("Tuesday\n"); case 3:printf("Wednesday\n"); case 4:printf("Thursday\n"); case 5:printf("Friday\n"); case 6:printf("Saturday\n"); case 7:printf("Sunday\n"); default:printf("error\n"); } } 本程序是要求输入一个数字,输出一个英文单词。但是当输入3之后,却执行了case3以及以后的所有语句,输出了Wednesday 及以后的所有单词。这当然是不希望的。为什么会出现这种情况呢?这恰恰反应了switch语句的一个特点。在switch语句中,“case 常量表达式”只相当于一个语句标号, 表达式的值和某标号相等则转向该标号执行,但不能在执行完该标号的语句后自动跳出整个switch 语句,所以出现了继续执行所有后面case语句的情况。 这是与前面介绍的if语句完全不同的,应特别注意。为了避免上述情况, C语言还提供了一种break语句,专用于跳出switch语句,break 语句只有关键字break,没有参数。在后面还将详细介绍。修改例题的程序,在每一case语句之后增加break 语句, 使每一次执行之后均可跳出switch语句,从而避免输出不应有的结果。 void main(){ int a; printf("input integer number: "); scanf("%d",&a); switch (a){ case 1:printf("Monday\n");break; case 2:printf("Tuesday\n"); break; case 3:printf("Wednesday\n");break; case 4:printf("Thursday\n");break; case 5:printf("Friday\n");break; case 6:printf("Saturday\n");break; case 7:printf("Sunday\n");break; default:printf("error\n"); } } 在使用switch语句时还应注意以下几点: 1.在case后的各常量表达式的值不能相同,否则会出现错误。 2.在case后,允许有多个语句,可以不用{}括起来。 3.各case和default子句的先后顺序可以变动,而不会影响程序执行结果。 4.default子句可以省略不用。程序举例 输入三个整数,输出最大数和最小数。 void main(){ int a,b,c,max,min; printf("input three numbers: "); scanf("%d%d%d",&a,&b,&c); if(a>b) {max=a;min=b;} else {max=b;min=a;} if(max<c) max=c; else if(min>c) min=c; printf("max=%d\nmin=%d",max,min); } 本程序中,首先比较输入的a,b的大小,并把大数装入max, 小数装入min中,然后再与c比较,若max小于c,则把c赋予max;如果c小于min,则把c赋予min。因此max内总是最大数,而min内总是最小数。最后输出max和min的值即可。 计算器程序。用户输入运算数和四则运算符, 输出计算结果。 void main(){ float a,b,s; char c; printf("input expression: a+(-,*,/)b \n"); scanf("%f%c%f",&a,&c,&b); switch(c){ case '+': printf("%f\n",a+b);break; case '-': printf("%f\n",a-b);break; case '*': printf("%f\n",a*b);break; case '/': printf("%f\n",a/b);break; default: printf("input error\n"); } } 本例可用于四则运算求值。switch语句用于判断运算符, 然后输出运算值。当输入运算符不是+,-,*,/时给出错误提示。 循环结构程序 循环结构是程序中一种很重要的结构。其特点是, 在给定条件成立时,反复执行某程序段,直到条件不成立为止。 给定的条件称为循环条件,反复执行的程序段称为循环体。 C语言提供了多种循环语句,可以组成各种不同形式的循环结构。 while语句 while语句的一般形式为: while(表达式)语句; 其中表达式是循环条件,语句为循环体。 while语句的语义是:计算表达式的值,当值为真(非0)时, 执行循环体语句。其执行过程可用图3—4表示。 统计从键盘输入一行字符的个数。 #include <stdio.h> void main(){ int n=0; printf("input a string:\n"); while(getchar()!='\n') n++; printf("%d",n); } 本例程序中的循环条件为getchar()!='\n',其意义是, 只要从键盘输入的字符不是回车就继续循环循环体n++完成对输入字符个数计数。从而程序实现了对输入一行字符的字符个数计数。 使用while语句应注意以下几点: 1.while语句中的表达式一般是关系表达或逻辑表达式,只要表达式的值为真(非0)即可继续循环。 void main(){ int a=0,n; printf("\n input n: "); scanf("%d",&n); while (n--) printf("%d ",a++*2); } 本例程序将执行n次循环,每执行一次,n值减1。循环体输出表达式a++*2的值。该表达式等效于(a*2;a++) 2.循环体如包括有一个以上的语句,则必须用{}括起来, 组成复合语句。 3.应注意循环条件的选择以避免死循环。 void main(){ int a,n=0; while(a=5) printf("%d ",n++); } 本例中while语句的循环条件为赋值表达式a=5, 因此该表达式的值永远为真,而循环体中又没有其它中止循环的手段, 因此该循环将无休止地进行下去,形成死循环。4.允许while语句的循环体又是while语句,从而形成双重循环。 do-while语句 do-while语句的一般形式为: do 语句; while(表达式); 其中语句是循环体,表达式是循环条件。 do-while语句的语义是: 先执行循环体语句一次, 再判别表达式的值,若为真(非0)则继续循环,否则终止循环。 do-while语句和while语句的区别在于do-while是先执行后判断,因此do-while至少要执行一次循环体。而while是先判断后执行,如果条件不满足,则一次循环体语句也不执行。 while语句和do-while语句一般都可以相互改写。 void main(){ int a=0,n; printf("\n input n: "); scanf("%d",&n); do printf("%d ",a++*2); while (--n); } 在本例中,循环条件改为--n,否则将多执行一次循环。这是由于先执行后判断而造成的。 对于do-while语句还应注意以下几点: 1.在if语句,while语句中, 表达式后面都不能加分号, 而在 do-while语句的表达式后面则必须加分号。 2.do-while语句也可以组成多重循环,而且也可以和while语句相互嵌套。 3.在do和while之间的循环体由多个语句组成时,也必须用{}括起来组成一个复合语句。 4.do-while和while语句相互替换时,要注意修改循环控制条件。 for语句 for语句是C语言所提供的功能更强,使用更广泛的一种循环语句。其一般形式为: for(表达式1;表达式2;表达3) 语句; 表达式1 通常用来给循环变量赋初值,一般是赋值表达式。也允许在for语句外给循环变量赋初值,此时可以省略该表达式。 表达式2 通常是循环条件,一般为关系表达式或逻辑表达式。 表达式3 通常可用来修改循环变量的值,一般是赋值语句。 这三个表达式都可以是逗号表达式, 即每个表达式都可由多个表达式组成。三个表达式都是任选项,都可以省略。 一般形式中的“语句”即为循环体语句。for语句的语义是: 1.首先计算表达式1的值。 2.再计算表达式2的值,若值为真(非0)则执行循环体一次, 否则跳出循环。 3.然后再计算表达式3的值,转回第2步重复执行。在整个for循环过程中,表达式1只计算一次,表达式2和表达式,3则可能计算多次。循环体可能多次执行,也可能一次都不执行。for 语句的执行过程如图所示。 void main(){ int n,s=0; for(n=1;n<=100;n++) s=s+n; printf("s=%d\n",s); } 用for语句计算s=1+2+3+...+99+100 int n,s=0; for(n=1;n<=100;n++) s=s+n; printf("s=%d\n",s); 本例for语句中的表达式3为n++,实际上也是一种赋值语句,相当于n=n+1,以改变循环变量的值。 void main(){ int a=0,n; printf("\n input n: "); scanf("%d",&n); for(;n>0;a++,n--) printf("%d ",a*2); } 用for语句修改例题。从0开始,输出n个连续的偶数。 int a=0,n; printf("\n input n: "); scanf("%d",&n); for(;n>0;a++,n--) printf("%d ",a*2); 本例的for语句中,表达式1已省去,循环变量的初值在for语句之前由scanf语句取得,表达式3是一个逗号表达式,由a++,n-- 两个表达式组成。每循环一次a自增1,n自减1。a的变化使输出的偶数递增,n的变化控制循次数。 在使用for语句中要注意以下几点 1.for语句中的各表达式都可省略,但分号间隔符不能少。如:for(;表达式;表达式)省去了表达式1。for(表达式;;表达式)省去了表达式2。 for(表达式;表达式;)省去了表达式3。for(;;)省去了全部表达式。 2.在循环变量已赋初值时,可省去表达式1,如例3.27即属于这种情形。如省去表达式2或表达式3则将造成无限循环, 这时应在循环体内设法结束循环。例题即属于此情况。 void main(){ int a=0,n; printf("\n input n: "); scanf("%d",&n); for(;n>0;) { a++;n--; printf("%d ",a*2); } } 本例中省略了表达式1和表达式3,由循环体内的n--语句进行循环变量n的递减,以控制循环次数。 void main(){ int a=0,n; printf("\n input n: "); scanf("%d",&n); for(;;){ a++;n--; printf("%d ",a*2); if(n==0)break; } } 本例中for语句的表达式全部省去。由循环体中的语句实现循环变量的递减和循环条件的判断。当n值为0时,由break语句中止循环,转去执行for以后的程序。在此情况下,for语句已等效于while( 1)语句。如在循环体中没有相应的控制手段,则造成死循环。 3.循环体可以是空语句。 #include"stdio.h" void main(){ int n=0; printf("input a string:\n"); for(;getchar()!='\n';n++); printf("%d",n); } 本例中,省去了for语句的表达式1,表达式3也不是用来修改循环变量,而是用作输入字符的计数。这样, 就把本应在循环体中完成的计数放在表达式中完成了。因此循环体是空语句。应注意的是,空语句后的分号不可少,如缺少此分号,则把后面的printf 语句当成循环体来执行。反过来说,如循环体不为空语句时, 决不能在表达式的括号后加分号, 这样又会认为循环体是空语句而不能反复执行。这些都是编程中常见的错误,要十分注意。 4.for语句也可与while,do-while语句相互嵌套,构成多重循环。以下形成都合法的嵌套。 (1)for(){…   while()    {…}   …     } (2)do{    …   for()    {…}   …   }while(); (3)while(){       …       for()        {…}       …      } (4)for(){     …     for(){     …      }     } void main(){ int i,j,k; for(i=1;i<=3;i++) { for(j=1;j<=3-i+5;j++) printf(" "); for(k=1;k<=2*i-1+5;k++) { if(k<=5) printf(" "); else printf("*"); } printf("\n"); } } 转移语句 程序中的语句通常总是按顺序方向, 或按语句功能所定义的方向执行的。如果需要改变程序的正常流向, 可以使用本小节介绍的转移语句。在C语言中提供了4种转移语句: goto,break, continue和return。 其中的return语句只能出现在被调函数中, 用于返回主调函数,我们将在函数一章中具体介绍。 本小节介绍前三种转移语句。 1.goto语句 goto语句也称为无条件转移语句,其一般格式如下: goto 语句标号; 其中语句标号是按标识符规定书写的符号, 放在某一语句行的 前面,标号后加冒号(:)。语句标号起标识语句的作用,与goto 语句配合使用。 如: label: i++; loop: while(x<7); C语言不限制程序中使用标号的次数,但各标号不得重名。goto语句的语义是改变程序流向, 转去执行语句标号所标识的语句。 goto语句通常与条件语句配合使用。可用来实现条件转移, 构成循环,跳出循环体等功能。 但是,在结构化程序设计中一般不主张使用goto语句, 以免造成程序流程的混乱,使理解和调试程序都产生困难。 统计从键盘输入一行字符的个数。 #include"stdio.h" void main(){ int n=0; printf("input a string\n"); loop: if(getchar()!='\n') { n++; goto loop; } printf("%d",n); } 本例用if语句和goto语句构成循环结构。当输入字符不为'\n'时即执行n++进行计数,然后转移至if语句循环执行。直至输入字符为'\n'才停止循环。 break语句 break语句只能用在switch 语句或循环语句中, 其作用是跳出switch语句或跳出本层循环,转去执行后面的程序。由于break语句的转移方向是明确的,所以不需要语句标号与之配合。break语句的一般形式为: break; 上面例题中分别在switch语句和for语句中使用了break 语句作为跳转。使用break语句可以使循环语句有多个出口,在一些场合下使编程更加灵活、方便。 continue语句 continue语句只能用在循环体中,其一般格式是: continue; 其语义是:结束本次循环,即不再执行循环体中continue 语句之后的语句,转入下一次循环条件的判断与执行。应注意的是, 本语句只结束本层本次的循环,并不跳出循环。 void main(){ int n; for(n=7;n<=100;n++) { if (n%7!=0) continue; printf("%d ",n); } } 输出100以内能被7整除的数。 int n; for(n=7;n<=100;n++) { if (n%7!=0) continue; printf("%d ",n); } 本例中,对7~100的每一个数进行测试,如该数不能被7整除,即模运算不为0,则由continus语句转去下一次循环。只有模运算为0时,才能执行后面的printf语句,输出能被7整除的数。 #include"stdio.h" void main(){ char a,b; printf("input a string:\n"); b=getchar(); while((a=getchar())!='\n'){ if(a==b){ printf("same character\n"); break; }b=a; } } 检查输入的一行中有无相邻两字符相同。 char a,b; printf("input a string:\n"); b=getchar(); while((a=getchar())!='\n'){ if(a==b){ printf("same character\n"); break; }b=a; } 本例程序中,把第一个读入的字符送入b。然后进入循环,把下一字符读入a,比较a,b是否相等,若相等则输出提示串并中止循环,若不相等则把a中的字符赋予b,输入下一次循环。 输出100以内的素数。素数是只能被1 和本身整除的数。可用穷举法来判断一个数是否是素数。 void main(){ int n,i; for(n=2;n<=100;n++){ for(i=2;i<n;i++) if(n%i==0) break; if(i>=n) printf("\t%d",n); } } int n,i; for(n=2;n<=100;n++){ for(i=2;i<n;i++) if(n%i==0) break; if(i>=n) printf("\t%d",n); } 本例程序中,第一层循环表示对1~100这100个数逐个判断是否是素数,共循环100次,在第二层循环中则对数n用2~n-1逐个去除,若某次除尽则跳出该层循环,说明不是素数。 如果在所有的数都是未除尽的情况下结束循环,则为素数,此时有i>=n, 故可经此判断后输出素数。然后转入下一次大循环。实际上,2以上的所有偶数均不是素数,因此可以使循环变量的步长值改为2,即每次增加2,此外只需对数n用2~n去除就可判断该数是否素数。这样将大大减少循环次数,减少程序运行时间。 #include"math.h" void main(){ int n,i,k; for(n=2;n<=100;n+=2){ k=sqrt(n); for(i=2;i<k;i++) if(n%i==0) break; if(i>=k) printf("\t%2d",n); } } 小结 1.从程序执行的流程来看, 程序可分为三种最基本的结构: 顺序结构,分支结构以及循环结构 2.程序中执行部分最基本的单位是语句。C语言的语句可分为五类: (1)表达式语句  任何表达式末尾加上分号即可构成表达式语句, 常用的表达式语句为赋值语句。 (2)函数调用语句  由函数调用加上分号即组成函数调用语句。 (3)控制语句  用于控制程序流程,由专门的语句定义符及所需的表达式组成。主要有条件判断执行语句,循环执行语句,转向语句等。 (4)复合语句  由{}把多个语句括起来组成一个语句。 复合语句被认为是单条语句,它可出现在所有允许出现语句的地方,如循环体等。 (5)空语句  仅由分号组成,无实际功能。 3.C语言中没有提供专门的输入输出语句, 所有的输入输出都是由调用标准库函数中的输入输出函数来实现的。 scanf和getchar函数是输入函数,接收来自键盘的输入数据。 scanf是格式输入函数, 可按指定的格式输入任意类型数据。 getchar函数是字符输入函数, 只能接收单个字符。 printf和putchar函数是输出函数,向显示器屏幕输出数据。 printf是格式输出函数,可按指定的格式显示任意类型的数据。 putchar是字符显示函数,只能显示单个字符。 4.关系表达式和逻辑表达式是两种重要的表达式, 主要用于条件执行的判断和循环执行的判断。 5.C语言提供了多种形式的条件语句以构成分支结构。 (1)if语句主要用于单向选择。 (2)if-else语句主要用于双向选择。 (3)if-else-if语和switch语句用于多向选择。 这几种形式的条件语句一般来说是可以互相替代的。 6.C语言提供了三种循环语句。 (1)for语句主要用于给定循环变量初值, 步长增量以及循环次数的循环结构。 (2)循环次数及控制条件要在循环过程中才能确定的循环可用 while或do-while语句。 (3)三种循环语句可以相互嵌套组成多重循环循环之间可以并列但不能交叉。 (4)可用转移语句把流程转出循环体外,但不能从外面转向循环体内。 (5)在循环程序中应避免出现死循环,即应保证循环变量的值在运行过程中可以得到修改,并使循环条件逐步变为假,从而结束循环。 7.C语言语句小结 名 称         一 般 形 式 简单语句       表达式语句表达式; 空语句; 复合语句        { 语句 } 条件语句       if(表达式)语句;            if(表达式)语句1; else语句2;            if(表达式1)语句1; else if(表达式2) 语句2…else语句 n; 开关语句        switch(表达式){ case常量表达式: 语句…default: 语句; } 循环语句       while语句            while(表达式)语句;            for语句 for(表达式1; 表达式2; 表达式3)语句;            break语句 break;            goto语句 goto;            continue语句 continue;            return 语句 return(表达式); 资料收集:beck Copyright 2002 www.vcok.com, All Rights Reserved

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

~忆缘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值