精华内容
下载资源
问答
  • 空间设计案例展示响应式网页模板
  • C语言程序设计案例式教程

    千次阅读 2019-05-30 17:00:09
    @[TOC] C语言程序设计案例式教程 欢迎指出文中错误及不足! 新的改变 我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客: 全新...

    C语言程序设计案例式教程

    数据类型与运算符

    1. 变量的数据类型 :整形变量,实型变量,字符型变量,枚举类型变量; (单引号内只可以存放转义字符、一个字符)

      数据类型所占字符数
      char1个字节
      int4个字节
      short2个字节

      枚举类型变量:eg:enum 枚举名{标识符1=整型常量1,标识符2=整型常量 2,…}

    2. printf()函数与scanf()函数
      (1) 常用printf()函数格式字符

      用格式字符含义注意
      %s输出一个字符串
      %c输出一个字符ACSLL码大写字母=小写字母+32
      %d以十进制输出一个有符号整型
      %u以十进制输出一个无符号整型eg:3为整数,3.为小数
      %f以十进制输出一个浮点数小数类型输出默认小数点后六位
    • sizeof运算符可以获取数据字节数 : sizeof(数据类型名称或变量名称)
      eg:printf(“int: %d字节\n”,sizeof(int));
    • 格式字符串一般形式:[标志][输出最小宽度][.精度][长度]类型
      (2) scanf()函数
      功能:接收用户输入,也可以通过格式控制字符控制用户输入
      接收的是变量的地址
    • scanf("%d,%d",&a,&b) 输入数据用逗号隔开
      scanf("%d%d",&a,&b) 输入数据用空格隔开
      (3)算数运算符优先级
      (4)位运算符
      (5)关系运算符
      (6)三目运算符
      表达式1?表达式2:表达式3(若1为真,则为2,否则为3)
      条件运算符方向:自右向左
      eg:a>b?a:c>d?c:d应理解为a>b?a:(c>d?c:d),这也是三目运算符的嵌套

    结构化设计程序

    1. if语句
      单分支结构:if…
      双分支结构:if…else…
      多分支结构:if…else if…else…

    2. switch条件语句(条件语句)

      - switch(表达式)
      - {
      - 	case 目标1:
      - 		执行语句 1;
      - 		break;
      -  	case 目标2:
      -   		执行语句 2;
      -   		break;
      -  	case 目标n:
      -   		执行语句 n;
      -   		break;
      - 	 default:
      -   		执行语句 n+1;
      -   		break;
      - }
      
    3. break语句(跳转语句)
      终止当前循环,执行循环体外的第一条语句

    4. continue语句(跳转语句)
      中止本次循环,并继续执行下一次循环
      break语句可以用于switch语句,而continnue不可以;

    5. goto语句(跳转语句)
      break语句出现在嵌套循环中的内层循环,只能跳出内层循环;如果想跳出外部循环则需要对外层循环添加标记,然后使用goto语句。

    6. while循环语句与 do…while循环语句
      循环条件的值非0,循环体就会被执行;循环体执行完毕时会继续判断循环条件,知道循环条件的值为0时,整个循环过程才会结束。 do…while循环语句与之类似。
      不同的是,do…while循环体先执行后判断(while先判断后执行)

    ...
    while(1)
    {
    	scanf("%d",&choice);
    	switch(choice)
    	{
    	case 1:
    		...
    		break;
    	 case 2:
     		 ...
     		 break;
     	case 3:
      		exit(0);
       		break;
    
    	}
    }
    
    1. for循环结构语句
      - for(初始化表达式;循环条件;操作表达式)
      - {
      - …
      - }
    2. 随机数(rand()函数,srand()函数)
      (1)rand()函数(一次性的
      生成一个无范围限制的随机数,只需使用rand()即可,rand()会返回一个随机数0~RAND_MAX(2147483647)。
      生成某个范围的随机数:
    范围写法
    0~10rand()%10(利用函数对10取余)
    5~25rand()%20+5(利用函数对25-5=20取余再加上5)

    (2)srand()
    称为随机数生成的初始化器
    函数原型:void srand(unsigned int seed);

    #include <stdio.h>
    #include <time.h>
    #include <stdlib.h>
    int main()
    {
    	srand((unsigned int)time(NULL));
    	int b1, b2, i;
    	b1=b2=0;
    	 for(i=0;i<4;i++)
    	 {
    		 b1=b1+rand()%6+1;
    		b2=b2+rand()%6+1;
    	 }
    	if(b1>b2)
    		 printf("b2 go\n");
    	 else if(b1<b2)
    		printf("b1 go\n");
    	else
    		printf("please again\n");
     	return 0;
    }   
    
    1. 自守数(某个数末尾平方的几位等于该数)
    在这里插入代码片
    
    1. 回文素数(穷举法)

    函数

    1. 函数的定义
    函数包括含义
    返回值类型限定函数返回值的数据类型
    参数名函数的名称
    参数类型限定调用函数时传入函数中的数据类型
    参数接收传入函数中的数据
    return关键字用于结束函数,将函数的返回值返回函数调用处
    返回值被return语句返回的值
    1. 函数调用时的数据传递:形式参数,实际参数

    2. 内存四区:
      (1)栈区: 对一个程序来说,栈区是一块连续的内存区域,该区域由编译器自动分配和释放,一般用来存放函数的参数、局部变量等,由于栈顶的地址和栈区的最大容量是由系统预先规定的,因此这块区域的内存大小固定若申请的内存空间超过栈区的剩余容量,则系统会提示溢出
      (2)堆区:对一个程序来说,堆可以是不连续的内存区域,此段区域可以=由程序开发者自主申请,其使用比较灵活,但缺点是同样需要程序开发人员自主释放,若程序结束时该段空间仍未被释放,就会造成内存泄露,最后由系统回收。
      (3)数据区
      根据其功能,数据区又可分为静态全局区和常量区两个城。
      全局区是用于存储全局变量和静态变量的区域,初始化为非0的全局变量和静态变量在一块区域,该区域称为data段;未初始化或者初始化为0的全局变量和静态变量在相邻的一块区城,该区域称为bss段。该区域在程序结束后由操作系统释放。
      常量区用于存储宇符事常量和其他常量,该区域在程序结束后由操作系统释放
      (4)代码区:代码区用于存放函数体的二进制代码。程序中每定义一个函数,代码区都会添加该函数的二进制代码,用于描述如何运行函数。当程序调用函数时,会在代码区寻找该函数的二进制代码并运行。

    3. 局部变量与全局变量
      (1)局部变量:定义在函数内部的变量,变量的作用域仅限于函数内部,函数执行完毕以后这些变量就失去作用。
      (2)全局变量:在所有函数(包括主函数)外部定义的变量成为全局变量,他不属于某个函数,而是属于源程序,因此全局变量可以为程序中的所有函数共用,他的有效范围是从定义开始处到源程序结束。
      若在同一个文件中,局部变量和全局变量同名,则全局变量会被屏蔽,在程序的局部暂时使用局部变量保存的数据。

    int a;   //a为全局变量
    int main()
    {
    	return 0;
    }
    
    1. 函数调用
      (1)主函数调用普通函数
      (2)嵌套调用
      主函数可以调用其他普通函数,普通函数可以相互调用,但是不能调用主函数
      (3)调用方式
    调用方式举例
    将函数作为表达式调用int a=max(1,2)
    将函数作为语句调用printf(“hello \n”);
    将函数作为实参调用printf("%d\n",max(1,2));
    1. 内部函数与外部函数
    内部函数外部函数
    关键字staticextern
    函数形式static 返回值类型 函数名(参数列表){}extren 返回值类型 函数名(参数列表){} eg:extren int add(int a,int b){}
    意义又被称为静态函数,指该函数仅在本文件有效可省略extren,仍可被其他文件调用
    1. 递归(即程序对自身的调用)
      需要注意两点:递归公式和边界条件(或终止条件)
      eg:(兔子数列或斐波那契数列或黄金分割数列)(存在错误)
    #include<stdio.h>
    int getnum(int n)
    {
    	if(n==1||n==2)
    		return 1;
    	return getnum(n-2)+getnum(n-1);
    }
    int main()
    {
    	printf("f(1)=%d\n",getnum(1));
    	printf("f(1)=%d\n",getnum(1));
    	printf("f(1)=%d\n",getnum(1));
    	printf("f(1)=%d\n",getnum(1));
    	printf("f(1)=%d\n",getnum(1));
    	printf("f(1)=%d\n",getnum(1));
    	return 0;
    }
    
    

    数组

    1. 一维数组
      (1)定义:数据类型 数组名[常量表达式];
      eg:int array[5]; (占据内存大小为:5*sizefo(int))
      (2)初始化常见方式:
    方式举例
    直接对数组中所有元素赋值int s[4]={1,2,3,4};
    只对数组中一部分元素赋值int s[5]={1,2} ; (其他元素的值会被默认设置为零)
    对数组全部元素赋值int[]={1,2,3} (系统会根据赋值号右边初始值列表给出的初值个数自动设置数组的长度)

    (3)引用方式:数组名[下标];
    (4) 数组的非法操作

    • 不能用已经初始化的数组为另一个数组赋值
      eg:int a[3]={1,2,3}; int b[3]; y=x;

    • 不能对数组进行整体的输入输出,必须以元素为单位进行操作
      printf()和scanf()只支持字符数组整体的输入输出

    • 数组和数组之间不能进行比较,也不能进行运算

    1. 二维数组
      (1)语法格式:类型说明符号 数组名 {常量表达式1}{常量表达式2};
      (2)初始化
    方式举例
    按行给二位数组赋初值int a[2][3]={{1,2,3},{4,5,6}};
    将所有的数组元素按顺序写在一个大括号里int a[2][3]={1,2,3,4,5,6};
    对部分数组元素赋初值int a[3][3]={{1},{2,3,4},{5,6}};(对没有赋值的元素,系统会自动赋值为0)

    二维数组的第一个下标可省略,第二个下标不可省略
    (3)引用(同一维数组一样)

    1. 数组作为函数参数
    func(int arr[3]);
    func(int arr[],int n);
    

    必须保证形参和实参的数组类型是相同的

    1. 冒泡排序法
      冒泡排序法便是最经典的排序算法之一,作为入门级排序算法,最适合编程新手学习。
      对于从小到大的冒泡排序,通俗来讲;不断地比较数组中相邻的两个元素,较小者向上浮,较大者往下沉,整个过程和水中气泡上升的原理相似。
      以从小到大排序为例,分步骤讲解冒泡排序的整个过程,具体如下:
      (1)从第一个元素开始,依次将相邻的两个元素进行比较,直到最后两个元素完成比较。如果前一个元素比后一个元素大,则交换位置。整个过程完成后,数组中最后一个元素就是最大值,这样便完成了第一轮的比较;
      (2)除了最后一个元素,将剩余的元素继续进行两两比较,过程与第(1)步相似,这样便可以将数组中第二大的数放在倒数第二个位置;
      (3)以此类推,重复上面的步骤,直到全部元素从小到大排列为止。
      eg:
    ...
    void bubblesort(int s[],int n)
    {
    	int i,j,temp;
    	for(i=0;i<n-1;i++)
    	{
    		for(j=0;j<n-j-1;j++)
    		{
    			if(s[j]>s[j+1])
    			{
    				temp=s[j];
    				s[j]=s[j+1];
    				s[j+1]=temp;
    			}
    		}
    	}
    }
    

    指针

    1. 定义指针变量的语法格式:变量类型* 变量名(变量名前的符号‘*’表示该变量为一个指针)
      eg:int *p
    2. 指针变量的初始化
      (1)接收变量的地址为其赋值,也可在定义时赋值
    int a=11;       //定义一个int型的变量a 
    int* p;         //定义一个int*型的指针变量p
    p=&a;           //使int.型的指针变量p指向int型变量a所在的存储空间
    
    int a=10;       //定义一个int型的指针变量a,并初始化为10
    int* p=&a;      //定义一个int*型的指针变量p,并初始化为变量a的地址
    

    (2)与其他指针变量指向同一块存储空间

    int* p;        //定义一个int型的指针变量p
    p=q;           //使int型的指针变量p与q指向同一块存储空间
    
    1. 指针的引用
      格式:*指针变量名
    int a=10;       
    int* p=&a;      
    printf("%d\n",&a);    //输出指针变量指向的地址中存储的数据(间接访问)
    //printf("%d\n",a);    //直接访问 
    
    

    只能使用间接访问的场合

    场合原因
    用户申请一块内存空间时因为该空间没有对应的变量名,所以只能通过首地址对其进行访问
    通过被调函数改变主调函数变量的值由于值只能由实参向形参单向传递,所以被调函数无法通过改变形参的值去改变主调函数中变量的值,只能通过间接访问指针指向的内存空间来改变主调函数的值。
    1. 指针类型
      (1)空指针(没有指向任一储存单元的指针)
    int* p1=0             //0是唯一一个不用转换就可以赋值给指针的数据 
    int*p2=NULL;          //NULL是一个宏定义,起作用与零相同  
      		      //在ASCLL码中编号为零的字符就是空
    

    一般编程时,先将指针初始化为空,在对其进行赋值操作

    int x=10;
    int *p=NULL;      //是指针指向空
    p=&x;
    

    (2)无类型指针(使用该指针为其他基类指针赋值,必须先转换成其他类型的指针,使用该指针,接收其他类型指针不需要强转

    void* p=NULL,*q;
    int* m=(int* )p;
    int a=10;
    q=&a;
    

    (3)野指针:指向不可用区域的指针。形成原因有以下两种:

    • 指针变量没有被初始化。定义的指针变量若没有被初始化,则可能指向系统中任意一块存储空间,若指向的存储空间正在使用,当发生调用并执行某种操作时,就可能造成系统崩溃,因此在定义指针时应使其指向合法空间。
    • 若两个指针指向同一块存储空间,指针与内存使用完毕之后,调用相应函数释放了一个指针与其指向的内存,却未改变另一个指针的指向,将其置空。此时未被释放的指针就变为野指针。
      在编程时,可以通过“if(p==NULL){}"来判断指针是否指向空,但是无法检测该指针时否为野指针,所以要避免野指针的出现。
    1. 指针的交换
      根据指针可以获得变量的地址,也可以得到变量的信息,所以指针交换包含两个方面,一是指针指向交换,二是指针所指地址中存储数据的交换。
      ( 1)指针指向交换
      若要交换指针的指向,首先需要申请一个指针变量,记录其中一个指针原来的指向,再使该指针指向另外一个指针,使另外一个指针指向该指针原来的指向。假设p和a都是int型的指针,则其指向交换示意图如图所示。
      在这里插入图片描述
      具体的实现方法如下:
    int *tmp=NUIL;       //创建辅助变量指针
    tmp=p;               //使用辅助指针记录指针口的指向
    p=q;                 //使指针p记录指针q的指向
    g=tmp;               //使指针q指向p原来指向的地址
    
    

    (2)数据的交换
    若要交换指针所指空间中的数据,首先需要获取数据,获取数据的方法在案例一中已经讲解,即使用“*”运算符取值。假设p和a都是int型的指针,则数据交换示意图如图所示。
    在这里插入图片描述

    int tmp=NUIL;         //创建辅助变量
    tmp=p*;               //使用辅助变量记录指针p指向的地址中的数据
    p=q;                  //使指针p指向地址中的数据放到q所指地址中
    p=tmp;                //将p中原来的数据放到q所指地址中
    
    1. 指针和一维数组

    一个普通的变量有地址,一个数组包含若干个变量,数组中的每个元素都在内存中占据存做单元,所以每个元素都有各自的地址。指针可以通过变量的地址访问相应的变量,当然也可以根据指针的指向来访问数组中的元素。
    以int型数组为例,假设有一个int型的数组,其定义如下:

    int a[5]={1,2,3,4,5};
    

    若要使用指针指向数组中的元素,则其方法如下;

    int* p=NULL;
    p=&a[0];              //也可写作p=a,是指针指向数组的首地址
    

    过指针访问数组中的其他元素,必须先定义一个指向该数组的指针

    本条定义语句与之前的赋值语另外需要注意的是,数组名是一个地址,在为指针赋值时不可再对其进行取址操作。本条赋值语句将数组的数组名赋给了指针p,此时p与数组名等价,所以可以像使用数组名一样,使用下标取值法对数组中的元素进行取值,其表示为p[下标]
    下标取值法指针的实质就是地址,其实对地址的加减运算并无意义,地址的值也不允许随意修改,但是当指针指向数组元素时,对指针进行加减运算能大大提高指针的效率。
    若数组指针与一个整数结合,则执行加法操作,例如对以上定义的,指向数组a的指针p,使p=p+1,则指针p将会指向数组中当前位置的下一个元素,即指向数组a1中的元素al1]。这是因为针对数组中的元素执行p+1操作时并非将地址的值进行简单的加1,而是根据数组元素的类型,加上一个元素所占的字节数。在本次p=p+1时,指针实际上加了4个字节(一个int型数据所占的字节),若指针p存储的 P存储的地址原本为0x2016,则运算后的指针存储的地址变为0x2020。
    举例:
    假设此时指针p指向数组元素a[0],若要使用指针获取数组元素a[2)的值,可以使用如下两种方式。
    (1)移动指针,使指针指向a[2],获取指针指向元素的值:

    p=p+2;
    printf (" &d",*p);
    

    (2)不改变指针指向,通过数组元素指针间的关系运算指针并取值:

    printf("%d",*(p+2));
    

    设要获取数组a中的元素a[3],则使用下标法和指针法取值的方式分别如下;

    p[3]               //下标取值法
    *(p+3)             //指针取值法
    

    (3)当指针指向数组元素时,还可以进行减法操作。
    此时指针类型相同,因此相减之后的结果为数组元素类型字节长度的倍数,根据这个数值,可以计算出两个元素之间相隔元素的个数.
    比如此时指针p1指向数组元素 a[1],指针p2指向数组元素a{3],则执行以下操作,

    (p2-p1)/sizeof (int);
    

    得到的结果为2,表示p1和p2所指的元素之间相隔两个元素,如此一来,不需要具体地知道两个指针所对应的数据,就可以知道它们的相对距离。
    两个指针(地址)相加没有意义

    1. 内存分配
      在程序执行的过程中,为保证程序能顺利执行,系统会为程序以及程序中的数据分配一定的存储空间。但是有些时候,系统分配的空间无法满足要求,此时需要编程人员手动申请堆上的内存空间来存储数据。
      C语言中申请空间常用的函数为: malloc()函数、calloc()函数和realloc()函数,这三个函数包含在头文件"stdlib.h"中,都能申请堆上的空间。
      (1) malloc()函数
      malloc()函数用于申请指定大小的存储空间,其函数原型如下:
    
    void* malloc(unsigned int size);
    
    

    在该原型中,参数size为所需空间大小。该函数的返回值类型为void*,使用该函数申请空间时,需要将空间类型强转为目标类型。假设要申请一个大小为16字节、用于存储整型数据的空间,则公式如下:

    
    int* s=(int*) malloc (16);
    
    

    当为一组变量申请空间时,常用到sizeof运算符,该运算符常用于求变量或数据类型在中所占的字节数。在调用malloc()等函数时使用sizeof运算符,可以在已知数据类型和数据数量的前提下方便地传入需要开辟空间的大小。假设为一个包含8个int型数据的数组申请存储空间,其方法如下所示:

    int* arr= (int*) malloc (sizeof (int) *8);
    

    (2) calloc()函数
    calloc()函数与malloc()函数基本相同,执行完毕后都会返回一个void*型的指针,只是在传值的时候需要多传入一个数据。其函数原型如下:

    void* calloc(unsigned int count,unsigned int size);
    

    calloc()函数的作用比malloc()函数更为全面。经calloc()函数申请得到的空间是已被初始化的空间,其中数据全都为0,而malloc()函数申请的空间未被初始化,存储单元中存储的数据不可知。另外calloc()在申请数组空间时非常方便,它可以通过参数size设置为数组元素的空间大小,通过参数将count设置为数组的容量。
    (3) realloc()函数

    realloc()函数的函数原型如下:

    void* realloc (voia* memory, unsignea int newsize);
    

    realloc()函数的参数列表包含两个参数,参数memory为指向堆空间的指针,参数newSize为新内存空间的大小,realloc()函数的实质是使指针memory指向存储空间的大小变为newSize.
    如果memory原本指向的空间大小小于newSize,则系统将试图合并memory与其后的空间,著能满足需求,则指针指向不变;如果不能满足,则系统重新为memory分配一块大小为newsize的空间。如果memory原本指向的空间大小大于或等于newsize,将会造成数据丢失

    1. 内存回收
      需要注意的是,使用malloc()函数、calloc()函数、realloc()函数申请到的空间都为堆空间,程序结束之后,系统不会将其自动释放,需要由程序员自主管理。" C语言提供了free()函数来释放由以上几种方式申请的内存, free()函数的使用方法如下;
    int* p= (int*) malloc (sizeof (int)*n);
    free (p);
    

    ==若用户申请的堆空间没有及时回收,可能会导致内存泄漏。==内存泄漏也称为“内存渗漏”,释放使用动态存储分配函数开辟的空间,在使用完毕后若未释放,将会一直占据该存储单元,直到程序结束。
    若发生内存泄漏,则某个进程可能会逐渐占用系统可提供给进程的存储空间,该进程运行时间越长,占用的存储空间就越多,直到最后耗尽全部存储空间,导致系统崩溃。
    内存泄漏是从操作系统的角度考虑的,这里的存储空间并非指物理内存,而是指虚拟内存大小,这个虚拟内存大小取决于磁盘交换区设定的大小。由程序申请的一块内存,如果没有指针指向它,那么就说明这块内存泄漏了

    1. 快速排序

    设要排序的数组是S[0]…S[N-1],首先任意选取一个数据(通常选用数组的第一个数)作为关键数据,然后将所有比键值小的数都放到键值之前,所有比键值大的数都放到键值之后,这个过程称为一趟快速排序。一趟快速排序的算法步骤如下:
    (1)设置两个变量low. high,排序开始的时候: low=0, high=N-1;
    (2)以第一个数组元素作为关键数据,赋值给key,即key=S[0]:
    (3)从high开始向前搜索,即从后向前搜索(high-- ),找到第一个小于key的值S[high]
    将S[high]和S[low]互换;
    (4)从low开始向后搜索,即从前向后搜索(low++ ),到第一个大于key的值Slow.
    将S[low]和S[high]互换:
    (5)重复步骤(3),( 4),直low>=high为止。
    需要特别注意的是,若在第(3), (4)步中,没找到符合条件的值,即(3)中S[high]不4于ney, (4) 中S[low]不大于key时,改变high, low的值,使得high=high-1, low=low+1 直至找到为止。找到符合条件的值,进行交换时,low, high指针位置不变。

    • 指针与二维数组
      (1)使用指针引用二维数组

    • eg:数组中的数据类型为int,每行有n个元素,则数组指针每加1,指针实际移动的步长为: n*sizeof(int).

    • 一般用数组名与行号表示一行数据。以数组a[2][3]={{1,2,3},{4,5,6}}为例, a[0]就表示第一行数据,a[1]表示第二行数据,a[0] a[1]相当于二维数组中一维数组的数组名,指向二维数组对应的第一个元素,a[0]=&a[0][0],a[1]=a[1][0]

    • a+i与"(a+i)的意义。通过之前一维数组的学习我们都知道""是它代表的是整行数据元素,只是一个地址,并不表示某一元素的值。""表示指针指向的地址存储的数据。但在二维数组中, a+i虽然指向的是该行元素 一个地址行元素的首地址,但与a[i]等价。*(a+i)表示二维数组元素a[i][j]的地址,等价于&a[i][j],也等价于a[i]+j:

      二维数组中相关指针与数据的表示形式

    表示形式含义
    a二维数组名,指向一维数组a[0],为0行元素首地址,也是a[0][0]的地址
    a[i],*(a+i)一维数组名,表示二维数组第i行元素首地址,值为&a[i][0]
    *(a+i)+j二维数组元素地址,二维数组中最小数据单元地址,等价于&a[i][j]
    ((a+i)+j)二维数组元素,表示第i行第j列数据的值,等价于a[i][j]

    (2)作为函数参数的二维数组

    • 一维数组的数组名就是一个指针,若要将一维数组传入函数,只需传入数组名,==传入的参数说明,应写为int* arr,而不是int** arr,因为int** arr是一个二级指针,他声明的是一个指向整形指针的指针,而非指向整形数组的指针。
    • 若将二维数组传入函数,形式相对略为复杂。一维数组可以不关心数组中数据的个数,但二维数组既有行,又有列,在定义时行值可以缺省,列值不能缺省,所以将二维数组的指针传递到函散中时必须确定数组的列值。定义一个数组指针的形式:数据类型 (*数组指针名)[列号]
    int(* p)[5]=a;
    func(p);
    

    (3)函数指针

    • 函数指针的定义
      若在程序中定义了一个函数,编译时,编译器会为函数代码分配一段存储空间,这段空间起始地址(又称入口地址)称为这个函数的指针。
    • 与普通变量相同,同样可以定义一个指针指向存放函数代码的存储空间的起始地址,这样的指针叫做函数指针。函数指针的定义格式:返回值类型(*变量名)(参数列表)
      其中返回值类型表示指针所指函数的返回值类型,“*p”表示这是一个指针变量,参数列表表示该指针所指函数的形参列表。
    • 假设定义一个参数列表为两个int型变量,返回值类型为int的函数指针,则其格式:int (p) (int,Int)
      "的优先级较高,所以要将“变量名”用小括号括起来。
      (4)函数指针的用途
    • 调用函数,使用函数指针调用对应函数,方法与使用函数名调用函数类似,只需将函数名替换为“指针名”即可。假设要调用指针p指向的函数,其形式如下, (*p) (3,5)
      二是将函数的地址作为函数参数传入其他函数。将函数的地址传入其他参数,就可以在被调函数中使用实参函数。函数指针作为函数参数的示例如下:
    void func(int (*p) (int, int), int b, int c);
    

    字符串

    1. 字符数组
      (1)字符数组的定义
    char 数组名[常量表达式1][常量表达式2];       //二维数组
    char 数组名[常量表达式1] ;                  //一维数组,char表示字符数据类型
    

    (2)字符数组的初始化
    在数组定义的同时,也可以对数组中的元素赋值
    元素个数不能多于字符数组大小
    初始值项少于数组长度,空余元素都会赋值为空字符‘\0’
    (3)字符串概念
    字符串是由数字、字母、下划线和空格等各种字符组成的一串字符,是个常量,由一对英文半角状态下的双引号(" )括起来。字符串在末尾都默认有一个’\0’作为结束符。
    方法.

    • 用字符串初始化字符数组
    • 获取字符串长度
      sizeof运算符也可以用来求字符串的长度,例如sizeof(‘abcde’)。还可以使用strlen()函数来获取字符串长度, strten()函数原型如下:unsigned int strlen(char *s)
      其中s是指向字符串的指针,返回值是字符串的长度。需要注意的是,使用strlen()函数得到的字符串的长度并不包括末尾的空字符‘\0’
      strlen()与sizeof运算符的区别,具体如下:
    • sizeof()是运算符; strlen()是 C语言标准库函数,包含在string.h头文件;
    • sizeof()的功能是获得所建立对象的字节大小,计算类型所占内存; strlen()时获得字符串所占内存的有效字节数;
    • sizeof运算符的参数可以是数组、指针、类型、对象和函数;strlen()函数的参数是指向以’\0’结尾的字符串的指针;
    • sizeof()运算符计算大小在编译时就完成,因此不能用来计算动态分配内存的大小,strlen()函数结果要在运行时才能计算出来。
    1. 字符串与指针
      在C语言中,字符型指针用char*来定义,它不仅可以指向一个字符型常量,还可以指个字符串。
      字符数组与字符指针:字符串用字符数组存储,也可以取数组地址赋值给字符型指针。以下为两者的区别与联系:
      (1)存储方式
      字符数组在用字符串初始化时,这个字符串就存放在了字符数组开辟的内存空间中;而字符指针变量在用字符串常量初始化时,指针变量中存储的是字符串的首地址,但字符串存储在常量区。
      上面的文字描述有些晦涩,下面通过一段示例代码来辅助理解,具体如下:
      在这里插入图片描述
      存储在栈区、堆区和静态区上的数据是可更改的,存储在常量区的数据只能在定义时赋值,且一旦赋值就不能再改变。
      (2)初始化及赋值方式
      初始化方式:可以对字符指针变量赋值,但不能对数组名赋值。
    char str[(6] ="hello";//char str[6]; str="hello",这种写法错误 
    char *p= "hello" //等价于char*p; p = "hello";
    

    赋值方式:使用数组定义的字符串只能通过为数组中的元素逐一赋值或通过调用复制函数的方式来赋值,而使用指针定义的字符串还可以实现直接赋值。

    char p1 = "hello", *p2;
     p2 = p1;
    char str1[6]="hello",str2[6];
    

    不可写成str1=str2,不可数组赋值

    (3)字符指针与数组名字符指针变量的值是可以改变的,而数组名是一个指针常量,其值不可以改 变。
    (4)字符串中字符的引用:可以用下标法和地址法引用数组元素和字符串中的字符元素。

    1. gets()函数与puts()函数
      (1)gets()函数读入用户输入的字符串时,会读取换行符之前所有的字符(不包括换行符本身),并在字符串的末尾添加一个空字符’\0’用来标记字符串的结束,读取到的字符串会以指针形式返回。
      原型:char* gets(char* str)
      (2)puts()函数接收的参数是一个字符串指针,该指针指向要输出的字符串,并且会自动在字符串末尾追加换行符"\n’。如果调用成功则返回一个int类型的整数,否则返回EOF
      原型:int puts(const char* str)
      ==puts()函数相比, printf()函数不会一次输出一整行字符串,而是根据格式化字符串输出一个个“单词”。由于进行了额外的数据格式化工作, printf()函数比puts()函数效率稍低。然而print()函数可以直接输出各种不同类型的数据,因此printf()函数比puts()函数应用更为广泛。
    2. 字符串连接函数
      在程序开发中,可能需要对两个字符串进行连接,例如将电话号码和相应的区号进行连接。就务调商
      为此, C语言提供了strcat)函数和strncat)函数来实现连接字符串的操作,这两个函数的相关解具体如下:
    • strcat()函数
      strcat()函数的用法很简单,它用来实现字符串的连接,即将一个字符串接到另一个字符串的后面。其函数原型如下所示:
      chat* strcat (char* dest, const char* src);
      表示将指针src指向的字符串接到指针dest指向的字符串之后。需要注意的是,在使用 strcat()函数之前, dest对应的字符数组必须要有足够的空间来容纳连接之后的字符串,否则会发生缓冲区溢出的问题
    • strncat()函数
      为了解决使用strcat()函数实现字符串连接时出现的“缓冲区溢出”问题,C语言提供了 strncat()函数。其函数原型如下:
      char* strncat (char* dest, const char* src, size_t n);
      strncat()函数除了接收两个字符指针src和dest之外,还接收第三个参数n,该函数的功能是:获取src所指字符串中的前n个字符,添加到dest所指字符串的结尾,覆盖dest所指字串结尾的’\0’,实现字符串拼接。
    1. 字符串复制函数
      strcpy()函数,该函数专门用于实现字符串的复制,其函数原型如下:
      char* strcpy (char* dest, const char* src);
      参数dest和src可以在字符串中的任意一个位置,字符串指针src所指向的字符串将被复制到dest所指向的字符串中。

    2. 字符串比较函数
      (1)strcmp()函数:比较两个字符串,其函数原型如下所示:
      int strcmp (const char* str1, const char* str2);
      (2)strncmp()函数:比较两个字符串的前n个字符。其函数原型如下所示:
      int strncmp (const char* strl, const char* str2, size_t n);
      strncmp()函数指定比较前n个字符,如果 str1和str2的前n个字符相同,则函数返回值为0。

    3. 选择排序算法:是在每一趟排序过程中从待排序记录中选择出最大(小)的元素,将其依次放在数组的最前或最后端的排序方法。

    4. 字符串查找函数
      (1) strchr()函数
      strchr()函数用来查找指定字符在指定字符串中第一次出现的位置,其函数原型如下所示;
      char* strchr (const char* str, char c);
      其中参数str为被查找的字符串, c是指定的字符。如果字符串str中包含字符c, strchr()函数将返回一个字符指针,该指针指向字符c第一次出现的位置;否则返回空指针。
      (2) strrchr()函数
      strrchr()函数用来查找指定字符在指定的字符串中最后一次出现的位置,其函数原型如所示:
      char* strrchr (const char* str, char c);
      其中参数str为被查找的字符串, c是指定的字符。如果字符串str中包含字符c,strrchr() 函数将返回一个字符指针,该指针指向字符c最后一次出现的位置,否则返回空指针。
      (3) strstr()函数
      上面两个函数都只能搜索字符串中的单个字符,如果要在字符串中搜索是否包含一个子字符串时,可以使用strstr()函数,其函数原型如下所示:
      char *strstr (const char *haystack, const char *needle);
      其中参数haystack是被查找的字符串, needle是子字符串。如果在字符串haystack到了字符串needle,则返回子字符串的指针,否则返回空指针。

    5. 字符串的其他常用函数
      (1)atoi()函数
      atoi()函数用于将一个数字字符串转换为对应的十进制数,其函数原型如下所示.
      int atoi (const char* str);
      atoi()函数接收一个数字字符串作为参数,返回转换后的十进制整数。如果转换失败,则返回0。需要注意的是, atoi()函数的声明位于stdlib中
      (2)itoa()函数
      用来将一个整数转换为不同进制下的字符串,其函数原型如下所示:
      char* itoa (int val, char* dst, int radix)
      第一个参数val表示的是待转换的数,第二个素表示的是目标字符数组,第三个参数表示的是要转换的进制。
      (3)sprintf()函数
      字符串格式化命令,主要功能是把格式化的数据写入某个字符串中. sprintf()函数和printf()函数都是变参函数。其函数原型如下所示:
      int sprintf( char *buffer, const chat "Eormat, [ argument]…);
      第一个参数表示目标字符数组,第二个参数表示格式化字符串,第三个参数表示需要转换的整数。

    编译和预处理(…)

    结构体和共用体(…)

    文件

    1. 计算机中的流
      在C语言中,将在不同的输入/输出等设备(键盘、内存、显示器等)之间进行传递的数据抽象为“流”。例如,当在一段程序中调用scanf()函数时,会有数据经过键盘流入存储器;当调用printf()函数时,会有数据从存储器流向屏幕。流实际上就是一个字节序列,输入函数的字节可被称为输出流。
      根据数据形式,输入输出流可以被细分为文本流(字符流)和二进制流。文本流和二进制流之间的主要差异是,在文本流中输入输出的数据是字符或字符串,可以被修改,而二进制流中输入输出的是一系列二进制的0、1代码,不能以任何方式修改。

    2. 文件
      (1)文件的概念
      文件是指存储在外部介质上的数据的集合。一个文件需要有唯一确定的文件标识,以使用户根据标识找到唯一确定的文件,方便用户对文件的识别和引用。文件标识包含三个部分,分别为文件路径、文件名主干、文件扩展名。
      在这里插入图片描述
      操作系统以文件为单位,对数据进行管理,若想找到存放在外部介质上的数据,必须先按照文件名找到指路径定的文件,再从文件中读取数据。
      (2)文件的分类
      计算机中的文件分为两类,一类为文本文件,另一类为二进制文件。
      文本文件又称为ASCII文件,该文件中一个字符占用一个字节,存储单元中存放单个字符对应的ASCI1码。假设当前需要存储一个整数数据21,则该数据在磁盘上存放的形式
      ’2‘(50)------->01010000
      ‘1’ (49) --------->00110001
      文本文件中的每个字符都要占用一个字节的存储空间,并且在存储时需要进行二进制和ASCIl码之间的转换,因此使用这种方式既消耗空间,又浪费时间。
      数据在内存中是以二进制形式存储的,如果不加转换地输出到外存,则输出文件就是一个二进制文件。二进制文件是存储在内存的数据的映像,也称为映像文件。若使用二进制文件存储,则存储空间更少且不需要进行转换,如此既节省时间,又节省空间。但是这种存放方法不够直观,需要经过转后才能看到存放的信息。

    3. 文件的缓冲区
      目前C语言使用的文件系统分为缓冲文件系统(标准1/O )和非缓冲文件系统(系统I/O)ANSI C标准采用“缓冲文件系统”处理文件。
      听谓缓冲文件系统是指系统自动在内存中为正在处理的文件划分出了一部分内存作为区。当从磁盘读入数据时,数据要先送到输入文件缓冲区,然后再从缓冲区逐个把数据传送序中的变量;当从内存向磁盘输出数据时,必须先把数据装入输出文件缓冲区,装满之后,数据从缓冲区写到磁盘。
      使用文件缓冲区可以减少磁盘的读写次数,提高读写效率。

    4. 文件指针
      文件指针的定义格式如下:在C语言中,所有的文件操作都必须依靠指针来完成,因此在对文件进行操作之前,必须先使指针与文件建立联系。
      FILE*变量名
      一个文件指针变量只能指向一个文件,也就是说,要操作多少个文件,就要定义同样数量的文件指针

    5. 文件的打开与关闭
      对文件进行读写之前,需要先打开文件;读写结束之后,则要及时关闭文件。
      (1)打开文件
      专门用于打开文件的函数fopen()函数,该函数的函数原型如下:
      FILE* fopen (char* filename, char* mode);
      其中返回值类型FILE*表示该函数返回值为文件指针类型;参数flename用于指定文件的绝对路径,即用来确定文件包含路径名、文件名主干和扩展名的唯一标识;参数mode用于指定文件的打开模式。
      文件正常打开时,函数返回指向该文件的文件指针;文件打开失败时,函数返回NULL。

    FILE* fp;
    fp.fopen ("D:\Itest.txt",");
    if (fp==NULL)
    {
    	printf ("File open error!\n");
    	exit (0);
    }
    

    一般在调用该函数之后,为了保证程序的健壮性,会进行一次判空操作
    (2)关闭文件:类似于在堆上申请内存,文件在打开之后也需要一步对应操作,即关闭文件。
    关闭文件的目的是释放缓冲区以及其他资源。若打开的文件不关闭,将会慢慢耗尽系统资源 C语言中专门用于关闭文件的函数fclose(), fclose()函数的函数原型如下:
    int fclose(FILE* fp)
    该声明的返回值类型为int,如果成功关闭则返回0,否则返回EOF (“end of tile”,是文件结束的标识,包含在头文件stdio.h中),函数中的参数fp表示待关闭的文件。

    (3)文件的打开模式

    打开模式名称描述
    r/rb只读模式以只读的方式打开一个文本文件/二进制文件,如果文件不存在或无法找到 fopen()函数调用失败,返回NULL
    w/wb只写模式以只写的方式创建一个文本文件/二进制文件,如果文件已存在,则创建新文件
    a/ab追加模式以只写的方式打开一个文本文件/二进制文件,只允许在该文件末尾追加数据,如果文件不存在,则创建新文件
    r+/rb+读取/更新模式以读/写的方式创建一个文本文件/二进制文件,如果文件不存在, fopen)函数调用失败,返回NULL
    w+/wb+写入/更新模式以读/写的方式创建一个文本文件/二进制文件,如果文件已存在,则重写文件
    a+ab+追加/更新模式打开一个文本文件/二进制文件,允许进行读取操作,但只允许在文件末尾添加数据,若文件不存在,则创建新文件

    6.写文件
    文件分为文本文件和二进制文件,因为它们的存放形式不同,所以写文件的方法也不一样。
    (1)写文本文件
    在对文本文件进行写操作时,主要用到两个函数,分别为: fputc()函数和fputs()函数。

    ① fputc()函数
    fputc()函数用于向文件中写入一个字符,其函数原型如下:
    int fputc (char ch, FIIE *fp);
    其中ch表示写入的内容,fp表示待写入文件的指针, int表示返回值类型。

    ② fputs()函数
    使用fputs()函数可以向文件中写入一个字符串(不自动写入字符串结束标记符’\0’),成功写入个字符后,文件位置指针会自动后移,函数返回值为非负整数,否则返回EOF。其函数原型如下:
    int fputs (const char* str, FILE file);
    其中参数str表示待写入的一行字符串;参数file表示待写入文件的指针; int表示返回值类型。
    (2)写二进制文件
    对二进制文件写操作主要使用fwrite()函数,原型为:
    unsigned Int fwrite (const void
    str, unsigned int size ,unsigned int count, FILE fil)
    参数str表示待写写入数据的指针;参数size表示待写入数据的字节数;参数count表示待写写入数据个数;参数file表示待写入数据的文件指针;返回值的类型unsigned int 为无符号整形。
    文本模式下具有特殊意义的字符(如‘\n’、’\0’),在二进制模式下没有意义
    (3) fprint ()函数
    除了从输入设备写入数据,还能从字符串中获取数据,写入文件中
    7. 读文件
    (1)读文本文件
    fgetc()函数:用于从文件中读取一个字符
    ② fgets()函数:每次从文件中读取一行字符串,或读取指定长度的字符串。
    (2)读二进制文件
    对二进制文件进行读操作主要使用fread()函数, fread()函数用于在程序中以二进制的形式读取文件,其函数原型如下:
    unsigned int fread (void
    dstBuf, unsigned int elementsize,unsigned int count, FILE* file);
    其中参数desBuf用于存储待接收数据的指针;参数elementSize表示要接收的数据项的字节数;参数count表示每次函数运行时要读取的数据项的个数;参数file为指向源文件的文件指针;返回值类型unsigned int表示函数返回值的类型为无符号整型。

    (3) fscanf()函数
    fscanf()函数用于从文件中格式化地读取数据,其函数原型如下:
    int fscan (FILE* file, const char * format, .)
    其中参数file表示指向文件的指针,参数format表示文件中的字符串输出时遵循的格式;返回值int表示函数返回值类型为整型。如果该函数调用成功,则返回输入的参数的个数;否则返回EOF。
    举例说明该函数的用法:
    Escant (Ip, “8sa”, work, age);
    因为数据只能从实参传递给形参,其中的参数应为指针变量,所以需要对整型变量age进行取址操作。
    7. 文件位置指针
    为了对读写进行控制,系统为每个文件设置了一个位置指针,用于指示文件当前读写的位置该指针被称为文件位置指针。
    当从文件头部开始,对文件进行顺序读写时,文件位置指针伴随着读写过程逐个后移,每读写一个数据,位置指针后移一个位置。下次读写开始时,系统会从文件位置指针指向的位置开始读写文件。
    文件位置指针也可以人为移动,实现文件的随机读写。常用的控制文件位置指针的函数有三个;

    1. seek)函数
      fseek()函数的作用是将文件位置指针移动到指定位置,其函数原型如下:
      int fseek (FILE* fp,long offset,int origin);
      其中参数tp表示指向文件的指针;参数offset表示以参数origin为基准使文件位置指针移动的偏移量;参数origin表示文件位置指针的起始位置,它有三个枚举值:
      SEEK-SET:该参数对应的数值为0,表示从文件起始位置开始偏移。
      SEEKEND:该参数对应的数值为2,表示相对于文件末尾进行偏移。
      SEEK-CUR:该参数对应的数值为1,表示相对于文件位置指针当前所在位置进行偏移。
      在调用该函数时,若调用成功则会返回0,若有错误则会返回-1,该函数一般用于二进制文件,因为对文本文件进行操作时,需要进行字符转换,对位置的计算可能会发生错误。
      (2) rewind()函数:可以将文件位置指针移动到文件的开头,其函数原型如下:
      void rewind (FILE* fp);
      (3) ftell()函数:获取文件位置指针当前指向的位置,其函数原型如下:
      long ftell (FILE* fр);
      == ftell()若调用成功,将返回文件位置指针当前所在的位量;若调用失败,则返回-1。==
    展开全文
  • 《数据结构课程设计案例精编(用C/C++描述)》

    千次下载 热门讨论 2008-10-08 10:33:11
    附:《数据结构课程设计案例精编(用C/C++描述)》一书简介 本书是数据结构案例教程,以软件重用为指导思想,以STL库中实现的数据结构(容器)为参照,重构了数据结构的视角和知识体系,并突出实战性和应用性。. 本书...
  • HBase列的可以动态增加,并且列为空就不存储数据,节省存储空间. hbase自动切分数据,使得数据存储自动具有水平scalability. Hbase可以提供高并发读写操作的支持。 HBase不能支持条件查询,只支持按照Row key来查询. ...

    一、概述

    HBase有以下几个特点:

    • HBase列的可以动态增加,并且列为空就不存储数据,节省存储空间.
    • hbase自动切分数据,使得数据存储自动具有水平scalability.
    • Hbase可以提供高并发读写操作的支持。
    • HBase不能支持条件查询,只支持按照Row key来查询.
    • 暂时不能支持Master server的故障切换,当Master宕机后,整个存储系统就会挂掉.

    因为HBase的这些特点,是它和Mysql的等关系型数据库的应用场景和设计理念完全不同。传统关系型数据库(mysql,oracle)数据存储方式主要如下:
    这里写图片描述
    上图是个很典型的数据储存方式,我把每条记录分成3部分: 主键、记录属性、索引字段 。我们会对索引字段建立索引,达到二级索引的效果。
    但是随着业务的发展,查询条件越来越复杂,需要更多的索引字段,且很多值都不存在,如下图:
    这里写图片描述
    上图是6个索引字段, 实际情况可能是上百个甚至更多,并且还需要根据多个索引字段刷选。查询性能越来越低,甚至无法满足查询要求。关系型数据里的局限也开始显现,于是很就出现了Nosql等非关系数据库。
    HBase作为一个列族数据库很强大,很多人就想把数据从mysql迁到hbase,存储的内容还是跟上图一样,主键设为为rowkey。其他各个字段的数据,存储一个列族下的不同列。但是想对索引字段查询就没有办法,目前还没有比较好的基于bigtable的二级索引方案,所以无法对索引字段做查询。这时候其实可以转换下思维,可以把数据倒过来,如下图:
    这里写图片描述
    把各个索引字段的值作为rowkey,然后把记录的主键和属性值按照一定顺序存在对应rowkey的value里。上图只有一个列族,是最简单的方式。 Value里的记录可以设置成定长的byte[],多个记录集合通过移位快速查询到。
    但是上面只适合单个索引字段的查询。如果要同时对多个索引字段查询,比如查询“浙江”and“手机”,需要取出两个value,再解析出各自的主键求交集。如果每条记录的属性有上百个,对性能影响很大。
    接下来的的问题就是解决多索引字段查询的问题。我们将主键字段和属性字段分开存储 ,储存在不同的列族下,多索引查询只需要取出列族1下的数据,再去最小集合的列族2里取得想要的值。储存如下图:
    这里写图片描述
    为什么是不同列族,而不是一个列族下的两个列?
    列族数据库数据文件是按照列族分的。在取数据时,都会把一个列族的所有列数据都取出来,事实上我们并不需要把记录明细取出来,所以把这部分数据放到了另一个列族下。
    接下来是对列族2扩展,列族2储存更多的列,用来做各种刷选、计算处理。如下图:
    这里写图片描述

    二、HBase和RDBMS的区别

    1、数据类型

    Hbase只有简单的字符类型,所有的类型都是交由用户自己处理,它只保存字符串。用户需要自己进行类型转换。而关系数据库有丰富的类型和存储方式。

    2、数据操作

    HBase只有很简单的插入、查询、删除、清空等操作,表和表之间是分离的,没有复杂的表和表之间的关系。而传统数据库通常有各式各样的函数和连接操作。

    3、存储模式

    HBase是基于列存储的,每个列族都由几个文件保存,不同的列族的文件时分离的。而传统的关系型数据库是基于表格结构和行模式保存的 。

    4、数据维护

    HBase的更新操作不应该叫更新,它实际上是插入了新的数据原来的旧版本仍然保留着,而传统数据库是替换修改。

    5、可伸缩性

    Hbase这类分布式数据库就是为了这个目的而开发出来的,所以它能够轻松增加或减少硬件的数量,并且对错误的兼容性比较高。而传统数据库通常需要增加中间层才能实现类似的功能。

    三、Hbase检索时间复杂度

    既然使用Hbase的目的是高效、高可靠、高并发的访问海量非结构化数据,那么Hbase检索数据的时间复杂度是关系到基于Hbase的业务系统开发设计的重中之重,Hbase的运算有多快,我们从计算机算法的数学角度做简要分析,以便读者理解后文的项目实例中Hbase业务建模及设计模式中的考量因素。
    我们先以如下变量定义Hbase的相关数据信息:

    n=表中KeyValue条目数量(包括Put结果和Delete留下的标记)
    b=HFile里数据库(HFileBlock)的数量
    e=平均一个HFile里面KeyValue条目的数量(如果知道行的大小,可以计算得到)
    c=每行里列的平均数量

    我们知道Hbase中有两张特殊表:-ROOT-&.META.,其中.META.表记录Region分区信息,同时,.META.也可以有多个Region分区,同时-ROOT-表又记录.META.表的Region信息,但-ROOT-只有一个Region,而-ROOT-表的位置由Hbase的集群管控框架,即Zookeeper记录。
    关于-ROOT-&.META.表的细节这里不再累述,感兴趣的读者可以参阅Hbase–ROOT-及.META.表资料,理解HbaseIO及数据检索时序原理。
    Hbase检索一条数据的流程如下图所示。
    这里写图片描述
    如上图我们可以看出,Hbase检索一条客户数据需要的处理过程大致如下:
    (1)如果不知道行健,直接查找列key-value值,则你需要查找整个region区间,或者整个Table,那样的话时间复杂度是O(n),这种情况是最耗时的操作,通常客户端程序是不能接受的,我们主要分析针对行健扫描检索的时间复杂度情况,也就是以下2至4步骤的内容。
    (2)客户端寻找正确的RegionServer和Region。花费3次固定运算找到正确的region,包括查找ZooKeeper,查找-ROOT-表,找找.META表,这是一次O(1)运算。
    (3)在指定Region上,行在读过程中可能存在两个地方,如果还没有刷写到硬盘,那就是在MemStore中,如果已经刷写到硬盘,则在一个HFile中。假定只有一个HFile,这一行数据要么在这个HFile中,要么在Memstore中。
    (4)对于后者,时间复杂度通常比较固定,即O(loge),对于前者,分析起来要复杂得多,在HFile中查找正确的数据块是一次时间复杂度为O(logb)的运算,找到这一行数据后,再查找列簇里面keyvalue对象就是线性扫描过程了(同一列簇的列数据通常是在同一数据块中的),这样的话扫描的时间复杂度是O(elb),如果列簇中的列数据不在同一数据块,则需要访问多个连续数据块,这样的时间复杂度为O(c),因此这样的时间复杂度是两种可能性的最大值,也就是O(max(c,elb)。
    综上所述,查找Hbase中某一行的时间开销为:

    O(1)用于查找region
    O(loge)用来在region中定位KeyValue,如果它还在MemStore中
    O(logb)用来查找HFile里面正确的数据块
    O(max(celb)用来查找HFile

    四、HBase的模式设计原则及优化

    1、列簇(clumn family)

    不要在一张表里定义太多的列簇,目前hbase不能很好处理超过3个列簇的表。hbase的flush和压缩是基于region的,当一个列簇所存储的数据达到flush阈值时,该表的所有列簇将同时进行flush操作,这将带来不必要的I/O开销。
    同时还要到同一个表中不同列簇所存储的记录数量的差别,即列簇的势。当列簇数量差别过大将会使包含记录数量较少的列簇的数据分散在多个region上,而region可能是分布在不同的regionserver上,这样当进行查询等操作,系统的效率会受到一定的影响。

    2、行健(row key)

    在HBase中,row key可以是任意字符串,最大长度64KB,实际应用中一般为10~100bytes,存为byte[]字节数组,一般设计成定长的。
    row key是按照字典序存储,因此,设计row key时,要充分利用这个排序特点,将经常一起读取的数据存储到一块,将最近可能会被访问的数据放在一块。
    其次,还要避免使用时序或单调行健。因为当数据到来时,hbase首先根据记录的行健来确定存储位置,及region位置。如果行健是时序或单调行健,那么连续到来的数据将会被分配到同一个region中,而此时系统中的其他region/regionserver将处于空闲状态,这是分布式系统不希望看到的。 可以将时序作为行键的第二个字段,并为行键添加一个前缀。

    3、尽量最小化行健和列簇的大小

    hbase中一条记录是由存储该值的行健,对应的列以及该值的时间戳决定。hbase中索引是为了加速随机访问的速度。该索引的创建是基于”行健+列簇:列+时间戳+值”的,如果行健和列簇的大小过大,将会增加索引的大小,加重系统的负担。

    4、版本数量

    创建表的时候,可以通过HColumnDescriptor.setMaxVersions(int maxVersions)设置表中数据的最大版本,如果只需要保存最新版本的数据,那么可以设置setMaxVersions(1)。HBase在进行数据存储时,新数据不会直接覆盖旧的数据,而是进行追加操作,不同的数据通过时间戳进行区分。默认每行数据存储三个版本,建议不要将其设置过大。

    5、存入内存

    创建表的时候,可以通过HColumnDescriptor.setInMemory(true)将表放到RegionServer的缓存中,保证在读取的时候被cache命中。

    6、TTL

    创建表的时候,可以通过HColumnDescriptor.setTimeToLive(int timeToLive)设置表中数据的存储生命期,过期数据将自动被删除,例如如果只需要存储最近两天的数据,那么可以设置setTimeToLive(2 * 24 * 60 * 60)

    7、合并和分片(compact&split)

    在HBase中,数据在更新时首先写入WAL 日志(HLog)和内存(MemStore)中,MemStore中的数据是排序的,当MemStore累计到一定阈值时,就会创建一个新的MemStore,并且将老的MemStore添加到flush队列,由单独的线程flush到磁盘上,成为一个StoreFile。于此同时, 系统会在zookeeper中记录一个redo point,表示这个时刻之前的变更已经持久化了(minor compact)。
    StoreFile是只读的,一旦创建后就不可以再修改。因此Hbase的更新其实是不断追加的操作。当一个Store中的StoreFile达到一定的阈值后,就会进行一次合并(major compact),将对同一个key的修改合并到一起,形成一个大的StoreFile,当StoreFile的大小达到一定阈值后,又会对 StoreFile进行分割(split),等分为两个StoreFile。
    由于对表的更新是不断追加的,处理读请求时,需要访问Store中全部的StoreFile和MemStore,将它们按照row key进行合并,由于StoreFile和MemStore都是经过排序的,并且StoreFile带有内存中索引,通常合并过程还是比较快的。
    实际应用中,可以考虑必要时手动进行major compact,将同一个row key的修改进行合并形成一个大的StoreFile。同时,可以将StoreFile设置大些,减少split的发生。

    五、HBase的表设计实例

    基于Hbase的系统设计与开发中,需要考虑的因素不同于关系型数据库,Hbase模式本身很简单,但赋予你更多调整的空间,有一些模式写性能很好,但读取数据时表现不好,或者正好相反,类似传统数据库基于范式的OR建模,在实际项目中考虑Hbase设计模式是,我们需要从以下几方面内容着手:

    1. 这个表应该有多少个列簇
    2. 列簇使用什么数据
    3. 每个列簇应有多少个列
    4. 列名应该是什么,尽管列名不必在建表时定义,但是读写数据时是需要的
    5. 单元应该存放什么数据
    6. 每个单元存储什么时间版本
    7. 行健结构是什么,应该包括什么信息

    以下我们以一个使用Hbase技术的客户案例为例来展示。

    1、场景介绍

    客户简介:客户是一个互联网手机游戏平台,需要针对广大手游玩家进行手游产品的统计分析,需要存储每个手游玩家即客户对每个手游产品的关注度(游戏热度),且存储时间维度上的关注度信息,从而能针对客户的喜好进行挖掘并进行类似精准营销的手游定点推送,广告营销等业务,从而扩大该平台的用户量并提升用户粘着度。
    该平台上手游产品分类众多,总共在500余以上,注册玩家(用户帐号)数量在200万左右,在线玩家数量5万多,每天使用手游频率峰值在10万/人次以上,年增量10%以上。
    根据以上需求,手游产品动态增长,无法确定哪些手游产品需要被存储,全部存储又会超过200列,造成大量空间浪费,玩家每天使用手游的频率及分类不确定,客户注册用户超百万,按天的使用热度数据量超过1000万行,海量数据也使得表查询及业务分析需要的集群数量庞大及SQL优化,效率低下,因此传统关系型数据库不适合该类数据分析和处理的需求,在项目中我们决定采用Hbase来进行数据层的存储的分析。

    2、高表设计

    让我们回到上文中设计模式来考虑该客户案例中表的设计,我们需要存储玩家信息,通常是微信号,QQ号及在该手游平台上注册的帐号,同时需要存储该用户关注什么手游产品的信息。而用户每天会玩一个或者多个手游产品,每个产品玩一次或者多次,因此存储的应该是该用户对某一手游产品的关注度(使用次数),该使用次数在每天是一个动态的值,而用户对手游产品也是一个多对多的key value键值的集合。该手游平台厂商关心的是诸如“XXX客户玩家关注YYY手游了么?”,“YYY手游被用户关注了么?”这类的业务维度分析。
    假设每天每个手游玩家对每个产品的关注度都存在该表中,则一个可能的设计方案是每个用户每天对应一行,使用用户ID+当天的时间戳作为行健,建立一个保存手游产品使用信息的列簇,每列代表该天该用户对该产品的使用次数。
    本案例中我们只设计一个列簇,一个特定的列簇在HDFS上会由一个Region负责,这个region下的物理存储可能有多个HFile,一个列簇使得所有列在硬盘上存放在一起,使用这个特性可以使不同类型的列数据放在不同的列簇上,以便隔离,这也是Hbase被称为面向列存储的原因,在这张表里,因为所有手游产品并没有明显的分类,对表的访问模式也不需区分手游产品类型,因此并不需要多个列簇的划分,你需要意识到一点:一旦创建了表,任何对该表列簇的动作通常都需要先让表offline。
    我们可以使用HbaseShell创建表,Hbaseshell脚本示例如下:
    这里写图片描述
    然后向该表中插入数据,最后存储的示例样本如下:
    这里写图片描述
    表设计解释如下:
    rowkey为QQ121102645$20141216表示帐号为QQ121102645的手游玩家(以QQ号联邦认证的)在2014年12月16日当天的游戏记录;列簇degeeInfo记录该行账户当天对每种产品类型的点击热度(游戏次数),比如SpaceHunter: 1表示玩(或者点开)SpaceHunter(时空猎人)的次数为1次。
    现在我们需要检验这张表是否满足需求,为此最重要的事是定义访问模式,也就是应用系统如何访问Hbase表中的数据,在整个Hbase系统设计开发过程中应该尽早这么做。
    我们现在来看,我们设计的该Hbase表是否能回答客户关心的问题:比如“帐号为QQ121102645的用户关注过哪些手游?”,沿着这个方向进一步思考,有相关业务分析的问题:“QQ121102645用户是否玩过3CountryBattle(三国3)手游?”“哪些用户关注了DTLegend(刀塔传奇)?”“3CountryBattle(三国3)手游被关注过吗?”
    基于现在的prodFocus表设计,要回答“帐号为QQ121102645的用户关注过哪些手游?”这个访问模式,可以在表上执行一个简单的Scan扫描操作,该调用会返回整个QQ121102645前缀的整个行,每一行的列簇进行列遍历就能找到用户关注的手游列表。
    代码如下:

    HTablePool pool = new HTablePool();
    HTableInterface prodTable = pool.getTable(“prodFocus”);
    Scan a = new Scan();
    a.addFamily(Bytes.toBytes(“degreeInfo”));
    a.setStartRow(Bytes.toBytes(“QQ121102645”));
    ResultScanner results = prodTable.getScanner(a);
    List<KeyValue> list = result.list();
    List<String> followGamess = new ArrayList<String>();
    for(Result r:results){
        KeyValue kv = iter.next();;
        String game =kv.get(1];
        followGames.add(user);
    }

    因为prodFocus表rowkey设计为用户ID $ 当天的时间戳,因此我们创建以用户“QQ121102645”为检索前缀的Scan扫描,扫描返回的ResultScanner即为该用户相关的所有行数据,遍历每行的“degreeInfo”列簇中的各个列即可获得该用户所有关注(玩过)的手游产品。

    第二个问题“QQ121102645用户是否玩过3CountryBattle(三国3)手游”的业务跟第一个类似,客户端代码可以用Scan找出行健为QQ121102645前缀的所有行,返回的result集合可以创建一个数组列表,遍历这个列表检查3CountryBattles手游是否作为列名存在,即可判断该用户是否关注某一手游,相应代码与上文问题1的代码类似:

    HTablePool pool = new HTablePool();
    HTableInterface prodTable = pool.getTable(“prodFocus”);
    Scan a = new Scan();
    a.addFamily(Bytes.toBytes(“degreeInfo”));
    a.setStartRow(Bytes.toBytes(“QQ121102645”));
    ResultScanner results = prodTable.getScanner(a);
    List<Integer> degrees = new ArrayList<Integer>();
    List<KeyValue> list = results.list();
    Iterator<KeyValue> iter = list.iterator();
    String gameNm =3CountryBattle”;
    while(iter.hasNext()){
        KeyValue kv = iter.next();
        if(gameNm.equals(Bytes.toString(kv.getKey()))){
            return true;
        }
    }
    prodTable.close();
    return false;

    代码解释:同样通过扫描前缀为“QQ121102645”的Scan执行表检索操作,返回的List<keyValue>数组中每一Key-value是degreeInfo列簇中每一列的键值对,即用户关注(玩过)的手游产品信息,判断其Key值是否包含“3CountryBattle”的游戏名信息即可知道该用户是否关注该手游产品。

    看起来这个表设计是简单实用的,但是如果我们接着看第三个和第四个业务问题“哪些用户关注了DTLegend(刀塔传奇)?”“3CountryBattle(三国3)手游被关注过吗?”
    如你所看到的,现有的表设计对于多个手游产品是放在列簇的多个列字段中的,因此当某一用户对产品的喜好趋于多样化的时候(productkey-value键值对会很多,意味着某一rowkey的表列簇会变长,这本身也不是大问题,但它影响到了客户端读取的代码模式,会让客户端应用代码变得很复杂。
    同时,对于第三和第四问题而言,每增加一种手游关注的key-value键值,客户端代码必须要先读出该用户的row行,再遍历所有行列簇中的每一个列字段。从上文Hbase索引的原理及内部检索的机制我们知道,行健是所有Hbase索引的决定性因素,如果不知道行健,就需要把扫描限定在若干HFile数据块中,更麻烦的是,如果数据还没有从HDFS读到数据块缓存,从硬盘读取HFile的开销更大,从上文Hbase检索的时间复杂度分析来看,现在的Hbase表设计模式下需要在Region中检索每一列,效率是列的个数*O(max(elb),从理论上已经是最复杂的数据检索过程。
    对关注该平台业务的客户公司角度考虑,第三个第四个的业务问题更加关注客户端获取分析结果的实时分析的性能,因此从设计模式上应该设计更长的行健,更短的列簇字段,提高Hbase行健的检索效率并同时减少访问宽行的开销。

    3、宽表设计

    Hbase设计模式的简单和灵活允许您做出各种优化,不需要做很多的工作就可以大大简化客户端代码,并且使检索的性能获得显著提升。我们现在来看看prodFocus表的另一种设计模式,之前的表设计是一种宽表(widetable)模式,即一行包括很多列。每一列代表某一手游的热度。同样的信息可以用高表(talltable)形式存储,新的高表形式设计的产品关注度表结构如下所示:
    这里写图片描述

    表解释:将产品在某一天被某用户关注的关联关系设计到rowkey中,而其关注度数据只用一个key-value来存储,行健Daqier_weixin1398765386465串联了两个值,产品名和用户的帐号,这样原来表设计中某一用户在某天的信息被转换为一个“产品-关注的用户”的关系,这是典型的高表设计。
    HFile中的key-value对象存储列簇名字。使用短的列簇名字在减少硬盘和网络IO方面很有帮助。这种优化方式也可以应用到行健,列名,甚至单元。紧凑的rowkey存储业务数据意味应用程序检索时,IO负载方面会大大降低。这种新表设计在回答之前业务关心的“哪些用户关注了XXXX产品?”或者“XXXX产品被关注过吗?”这类问题时,就可以基于行健使用get()直接得到答案,列簇中只有一个单元,所以不会有第一种设计中多个key-value遍历的问题,在Hbase中访问驻留在BlockCache的一个窄行是最快的读操作。从IO方面来看,扫描这些行在一个宽行上执行get命令然后遍历所有单元相比,从RegionServer读取的数据量是相同的,但索引访问效率明显大大提高了。
    例如要分析“3CountryBattles(三国群雄)手游是否被QQ121102645用户关注?”时,客户端代码示例如下:

    HTablePool pool = new HTablePool();
    HTableInterface prodTable = pool.getTable(“prodFocusV2”);
    String userNm =“QQ121102645”;
    String gameNm =“3CountryBattles”;
    Get g = new Get(Bytes.toBytes(userNm+”$”+gameNm));
    g.addFamily(Bytes.toBytes(“degreeInfo”));
    Result r = prodTable.get(g);
    if(!r.isEmpty()){
        return true;
    }
    table.close();
    return false;
    

    代码解释:由于prodFocusV2的rowkey设计改为被关注产品$用户Id的高表模式,手游产品及用户信息直接存放在行健中,因此代码以手游产品名“3CountryBattles$”加用户帐号“QQ121102645”的Byte数据作为Get键值,在表上直接执行Get操作,判断返回的Result结果集是否为空即可知道该手游产品是否被用户关注。

    4、其他优化

    当然还有一些其他优化技巧。你可以使用MD5值做为行健,这样可以得到定长的rowkey。使用散列键还有其他好处,你可以在行健中使用MD5去掉“$”分隔符,这会带来两个好处:一是行键都是统一长度的,可以帮助你更好的预测读写性能。第二个好处是,不再需要分隔符后,scan的操作代码更容易定义起始和停止键值。这样的话你使用基于用户$手游名的MD5散列值来设定Scan扫描紧邻的起始行(startRow和stopRow)就可以找到该手游受关注的最新的热度信息。
    使用散列键也会有助于数据更均匀的分布在region上。如该案例中,如果客户的关注度是正常的(即每天都有不同的客户玩不同的游戏),那数据的分布不是问题,但有可能某些客户的关注度是天生倾斜的(即某用户就是喜欢某一两个产品,每天热度都在这一两个产品上),那就会是一个问题,你会遇到负载没有分摊在整个Hbase集群上而是集中在某一个热点的region上,这几个region会成为整体性能的瓶颈,而如果对Daqier_weixin$1398765386465模式做MD5计算并把结果作为行键,你会在所有region上实现一个均匀的分布。
    使用MD5散列prodFocusV2表后的表示例如下:
    这里写图片描述

    展开全文
  • 2015前端设计案例与趋势

    千次阅读 2015-02-10 17:37:23
    扁平化设计  扁平的设计是2013年出现的最大的趋势之一,在新的一年里将会蓬勃发展。以苹果公司为例,其最新推出的 iOS7 就是一... 扁平化设计的优秀案例和素材: 20个精美的国外扁平化网页设计作品欣赏16个

    扁平化设计

      扁平的设计是2013年出现的最大的趋势之一,在新的一年里将会蓬勃发展。以苹果公司为例,其最新推出的 iOS7 就是一个典型的扁平设计产品,剥离多余的设计元素,包括去除传统的阴影,纹理和渐变模式的界面,基本上只使用平面形状,帮助用户有一个更方便的体验。

     

     

      扁平化设计的优秀案例和素材:

     

    个性排版

      越来越多的设计师在设计项目中采用有趣的字体和并把他们的个性注入其中。他们采用的字体比标准的衬线或非衬线体更精致,我们已经看到很多的设计师开始在他们的网页作品和LOGO作品中运用独特的个性化的字体。

     

     

      排版和字体在网页设计中的应用:

     

    滑出菜单

      想要能够流畅的查看网站的内容,不会被分心的事打断?滑出菜单允许你做到这一点。滑出式菜单可以从屏幕的顶部或侧面以无缝的切换方式呈现网站内容的其余部分,不会打断你的浏览体验。这是一个很好的方式,它的简约和简单是未来的发展趋势。

     

     

      视差滚动在网页中的应用:

     

    视频背景

      我以前向大家分享过很多大照片背景在网页设计中应用的案例,如今这种设计正在转变。大家都知道“一张图片胜过千言万语”,但在我看来,一段视频胜过千张照片。如果使用得当,大背景的视频可以让你的网站脱颖而出,让客户和读者以前所未有的方式记住你的品牌。

     

     

      在网页中应用大图片、视频作为背景的案例:

     

    无限滚动

      通过网站滚动要比通过点击链接访问各种信息速度更快,更容易。当然,这里说的无限滚动不是过去的内容杂乱的长的滚动页面。新的设计技术让内容可以被组织和格式化得更容易阅读和理解。

     

      

      设计人员可能会问,为什么无限滚动非常有用?首先,它提供了一个无缝的界面,无需重新加载页面。但是,用户要为不同的页面生成永久链接就困难了,而这一切是真实存在的问题的,所以说并不是每一个网站都适合使用无限滚动。我觉得无限滚动最好的地方是,你可以不断加载信息而又不需要一个特定的分页样式。

      在网页中应用大图片、视频作为背景的案例:

     

    简单的配色

      2014年我们将看到更多只使用一种或两种颜色的网站,而不是令人瞠目的图形和动画网站。一个新的趋势是使用一种明亮和干净的背景颜色,如红色,橙色或蓝绿色,制作出来的效果超简约和人性化。

     

      

      下面向大家推荐一批非常优秀的简约风格网站作品案例,相信你能从中获得很多的设计灵感:

     

    移动优先的设计

      响应网页设计是2013年的另一个大的趋势,在2014年我们将看到更多明确为移动使用而设计的网站。响应设计的理念并不仅仅只网站能够缩放到更小的尺寸,设计师的意识形态也应该发生改变。这里向大家推荐 DesignShack 上的一篇文章,其中谈到了这个想法。

      往往更容易规划最重要的界面元素,让他们排布成为一个移动的布局。如果这些元素不合适这样,你将不得不删除一些。同时,你可以规划布局将如何应对的窗口变大。您将有空间包含一个侧边栏,也可能是2个,以及许多其它的页面元素。

     

     

      A List Apart 上面有一本书叫《Mobile First》,它是由 Luke Wroblewski 编写的,他带来了很多和这里同样的想法。一次性制作移动优先的设计往往很困难,可以从小的尝试开始,慢慢改进。

      移动网站设计和响应式网站设计的优秀案例:

     

    文字变少,视频变多

      越来越多的公司不喜欢长篇累赘的介绍公司是做什么的,企业都转而选择短片来进行介绍。因为影片分享到其它的网站或者社会媒体上,更容易吸引注意力。视频是与观众进行有效的沟通的一个伟大的方式。

     

      

    滚动动画

      交互性的网页设计已经开始进入一个全新的水平。借助滚动动画,您可以创建令人难以置信的效果,你的观众可以互动和探索。以前的网页就是死板的,让人感觉没有互动,使用滚动动画后,用户就仿佛觉得进入了数字世界。

     

     

      视差滚动在网页中的应用:

     



    2014年快结束了,接近年底各种忙,忙里偷闲也别忘记自我充电。来看看明年2015年的网页设计趋势吧!没准能找到不少灵感呢!


    1、扁平化继续(或者Material Design设计语言崛起)


      

    扁平化设计在过去两年里势头迅猛,2015年在持续。然后,对于扁平化而言或许只是个概念,也许是Material Design(材料设计),那么什么是Material Design呢?

      

    Material Design是Google今年新推出的移动设计方向,“Material(材料)”是种隐喻:空间的合理化及系统动效的统一。Material源自现实的感知,灵感来自纸张和油墨,尚未开启的想象力和神奇。

      

    很多设计师说,材料设计语言在扁平化上作了微妙的渐变,层次感和动画保留了意义上的有形世界(物理空间和物体),同时实现了扁平化的所有优点。有些人可能不同意,但我个人觉得,这就是扁平化设计的整体领导,我们期待看到更多的企业和个人在2014年及以后采取该策略。

     

    2、幽灵按钮


      

    幽灵按钮凭借简洁时髦,及微妙的动画招人喜欢。2015年幽灵按钮将继续,特别是用在大背景和背景视频上更适用,这类设计在国外的网站使用比较多。

      

    3、更强调字体


      

    传统的一些Web字体价格昂贵,意味着网站排版需要更多的预算。如今,这种情况在改变,设计师只要更少的预算,字体集就能在网页上自由设计,甚至不需要任何费用,像突唯阿、weebly这样的建站平台就可免费使用。

      

    4、比大更大的图片,视频背景


      

    一个让网站脱颖而出的简单方式是突出关键的内容,这种趋势需要以美妙的方式来完成,强调设计哲学,优雅有力量,而非只是噱头。

      

    5、滚动主导点击


      

    得益于移动互联网的发展,网页设计更偏向移动化,因此会带来更有效和愉悦的移动体验,如滚动主导点击。这种方式更直观,易于实现,减少加载时间,允许网站与用户之间更多的交互。

      

    6、卡片式设计继续


      

    卡片式设计,不算新颖,却是响应式网页设计的最佳实践。卡片式设计很好的一个方式是模块化,重新编排栏目也不会草率或紊乱,在浏览器中能浏览大量数据,但是要提示用户深入了解。总之,卡片设计干净简单,具备多功能性。正是网络的需求,2014和2015年你将能看到更多卡片式风格设计。

      

    7、响应式在继续




    OK,也许你不打算打道回府。也许你有一个很好的理由不用响应式网页设计?在过去的几年里,响应设计快速巩固了自己作为网页设计的新标准。当然,也有争论,但是没人说,“让我们摆脱响应式设计吧”。实际上,越来越多的网站选择响应式的方向。2014年确实如此,2015年也还会继续,这已经不是种趋势,而是常态了。


    虽然,目前真正专注响应式网站建设的公司并不多,像weebly这样的知名建站公司也没涉及,而国内的话,也只有突唯阿比较专业。但是,像突唯阿这种完全可视化的建站平台,不需要编码和设计,整个网站的内容编辑只需要拖拽即可完成。甚至连非专业建站人士也能快速创建响应式网站,而且性价比也比较高,这种专业的且门槛比较低的建站平台,具有很好的发展前景,特别是在响应式设计逐渐常态化的时局下。


    8、微交互


      

    微交互也是个很好的势头。它们通常用在产品的体验和瞬间,网站简单的用户任务处理也会用到。一个简单的例子是注册的时候弹出框效果。微交互促进了用户的参与,这种趋势在未来几年将进一步渗透到网页设计中。

      

    9、交互式故事


      

    你想把所有的内容聚在一起吗?当然,不是每个页面都适合讲一个仙女的故事的。如果你的品牌由一系列概念或价值(优雅、创造力,简约等),从页面布局到字体选择,你都能通过页面元素和交互性故事来阐述品牌和理念,如特斯拉的官网。

      

    10、个性化用户体验


      

    使用cookies技术向用户展示相关内容不是什么新鲜事。然后,某些垃圾做法(如弹出选择),现在有了更好的设计和最佳实践,使用cookies显示特定的内容给用户比垃圾邮件和无耻的销售有效的多。Netflix会记住你最近看的,YouTube也如此。难道这些传统的大网站只会使用侧边栏小工具,以便你快速访问你喜欢的内容或评论的文章?或者突出查看过的内容或高亮帖子?我不这么认为,未来更会出现更优雅的技术。

     

    极好的例子Apple


      

    你不必是个苹果迷才能欣赏这么好的网页设计。苹果总是设法让它们的商标保持简单,继续设法挤出显著数量的当前和未来的设计趋势。如果你想学习但不想模仿它,不是你必须创建一个站点酷似他们,而是复制其显著的微妙之处,避免故意使用趋势的噱头。

      

    Tesla


     

    Go Electric page这个页面使用了大图片,长滚动和嵌入式信息图和交互式故事解释了他们产品的常见5大问题,这是个相当聪明的设计,相比传统枯燥无味的网站,这是个令人惊叹的展示。(互联网的一些事)




    展开全文
  • C++程序设计案例实训教程第1章

    千次阅读 2017-10-29 15:49:27
    若要学会一门程序设计语言,必须得多动手、多实践。本书就是一本用案例的方式帮助读者理解C++技术的入门书。说它入门,是因为本书的案例都是从最基本的C++语法入手,这样会让读者学得更轻松、理解更深刻。 在编写C++...

    1章  用最简单的案例讲述C++

    (本资料qq讨论群112133686)

    若要学会一门程序设计语言,必须得多动手、多实践。本书就是一本用案例的方式帮助读者理解C++技术的入门书。说它入门,是因为本书的案例都是从最基本的C++语法入手,这样会让读者学得更轻松、理解更深刻。

    在编写C++程序之前,读者必须了解C++的一些基本特征,如定义变量、实现输入和输出、定义数据结构来保存要处理的数据。不同的对象定义不同的变量,如整数变量、浮点数变量或字符串变量。定义变量后才能对变量进行处理,处理过程中可能涉及变量之间的转换。

    本章从最简单的数学计算开始,讲解C++语言支持的5种数学运算符:加(+)、减(-)、乘(*)、除(/)和取模(%)。

    1.1  C++程序的结构

    C++程序编排按照“缩进”方式,程序的注释以符合“//”引出  。一个C++程序中必须有一个主程序。标识函数“().,函数体语句大致由变量定义、执行语句和返回语句三部分组成。C++程序基本元素有:字符集、标识符、基本数据类型和变量定义。C++有丰富的运算符,运行表达式用来说明一个计算过程,由操作符和操作数组成,其中操作数包含常量、变量或函数调用。

    案例1-1显示“Hello World!

    【案例描述】

    下面介绍第一个程序,其功能是在屏幕上显示Hello World!”。虽然这个程序非常简单,但它展示了每个C++程序的基本组成结构。本例效果如图1-1示。

     

    1-1  在屏幕上显示欢迎界面(cout

    【实现过程】

    1)包含本例代码需要用到的两个头文件;

    2)显示提示性字符串;

    3)暂停,等待用户的输入;

    4)用户输入任意键退出程序。

    详细代码如下:

    #include<iostream.h> //包含头文件

    #include<iostream>

    int main() //主函数

    {

      cout << "Hello World!" << endl;    //标准输出流

      cout << "Welcome to C++ Programming" << endl;

      system("pause");

      return 0; //返回语句

    }

    【案例分析】

    1)每个C++程序都包含一个或多个函数,而且必须有一个命名为main的函数,main()是所有程序运行的起始点,不管它是在代码的开头、结尾还是中间。程序由执行函数功能的语句组成。以两个斜线符号(//)开始的程序行都被认为是注释行。以#标志开始的句子是预处理器的指示语句,如#include<iostream.h>告诉编译器的预处理器将输入/输出流的标准头文件包含在本程序中。

    2cout <<把一串字符串插入到输出流(控制台输出)中,cout的声明在头文件iostream.h中。return 0;是返回语句,表示main()函数执行完毕。

     

    提示:C++中,语句的分隔是以分号(;)为分隔符隔开的。并不是程序中所有的行都会被执行,如程序中的注释行(以//开头)、编译器预处理器的指示行(以#开头)、函数的声明(本例中的main函数)等,这些行都不会被执行。

    1.2  常量

    所谓的常量是指其值在程序的运行中不允许改变的量,分为字面常量和符号常量。包括整形常量、实型常量、字符常量、字符串常量和布尔常量。

    案例1-2 各类常量的使用例子

    【案例描述】

    一个常量(constant)是一个有固定值的表达式。C++语言中的字用来在程序源码中表达特定的值,即用来给变量赋予特定的值。本例详细介绍了各种常量的定义,效果如图1-2所示。

     

    1-2  各类常量的使用例子

    【实现过程】

    该程序用预处理器指令#define定义常量和用const前缀定义指定类型的常量,然后分别定义整数、浮点数、字符、字符串和布尔型数据类型的常量。其代码如下:

    #include <iostream>

    #include <iostream.h>

    #define PI 3.14159265

    #define NEWLINE '\n' //换行符newline

    #define WIDTH 100

    double circle;

    void main()

    {

    int r=2.2;

    circle = 2 * PI * r;

    std::cout <<"预处理器指令#define输出演示:"<<circle<<NEWLINE;

    system("pause");

     

    const int width = 100;

    const char tab = '\t'; //跳跃符tabulation

    const zip = 12440; //整数

    std::cout <<"const前缀定义常量输出演示:"<< width<<tab<<zip<<NEWLINE;

    system("pause");

     

    int i1=75; //十进制 decimal

    int i2=0113;               //八进制 octal

    int i3=0x4b; //十六进制 hexadecimal

    std::cout<<"进制输出演示:"<<i1<<i2<<i3<<NEWLINE;

    system("pause");

     

    int j1=75; //整型

    unsigned int j2=75u ;       //无符号整型

    long j3=75l;               //长整型

    unsigned long j4=75ul ;     //无符号长整型

    std::cout<<"常数数据类型输出演示:"<<j1<<j2<<j3<<j4<<NEWLINE;

    system("pause");

     

    float f1=3.14159;   // 3.14159

    float f2=6.02e23;   // 6.02 x 10^1023

    float f3=1.6e-19;           // 1.6 x 10^-19

    float f4=3.0 ;               // 3.0

    std::cout<<"浮点数输出演示:"<<f1<<f2<<f3<<f4<<NEWLINE;

    system("pause");

     

    //字符和字符串

    char a1='\n';

    char a2='\t';                //回车

    char *a3="Left \t Right" ;

    char *a4="one\ntwo\nthree" ; //定义字符串

    std::cout<<"字符和字符串常量输出演示:"<<a1<<a2<<a3<<a4<<NEWLINE;

    system("pause");

    //布尔型常量

    bool b1=true;

    bool b2=false;

    std::cout<<"布尔型常量输出演示:"<<b1<<b2<<NEWLINE;

    system("pause");

    return;

    }

    【案例分析】

    1C++常量有:整数(Integer Numbers)、浮点数(Floating-Point Numbers)、字符(Characters)、字符串(Strings)和布尔型常量(Boolean Literals)。整型常数表示十进制整数值。除十进制整数外,C++还允许使用八进制(octal numbers)和十六进制(hexadecimal numbers)的字常量(literal constants)。浮点数以小数(decimals)和指数幂( exponents)的形式表示。C++程序中还有表示特殊的字符,例如,换行符newline (\n)或跳跃符tab (\t),所有这些符号的前面要加一个反斜杠(\)。布尔型只有两个有效的值:truefalse,其数据类型为bool

    2)使用预处理器指令#define,可以将那些经常使用的常量定义为自己取的名字,而不需要借助于变量。格式是:#define identifier token-stringopt,如代码中的#define PI 3.14159265。通过使用const前缀,可以定义指定类型的常量,就像定义一个变量一样,如代码中的const int width = 100。

     

    提示:字用来在程序源码中表达特定的值,在代码中已经用了很多字来给变量赋予特定的值。例如:j1=75,其中的75就是一个字常量。

    1.3  变量与基本类型

    与常量相反,变量是程序的执行过程中其值可以变化的量。它的标识是用名字,是程序中最活跃的基本元素。变量的声明形式:数据类型 变量名1,变量名2,变量名n,如:int num,;等。变量必须先定义,定义的同时可以给它赋以初值。

    案例1-3取之不尽的箱子

    【案例描述】

    变量代表了一个值,这个值是不固定的。使用变量可以处理各种数据,并通过一定的控制程序来完成指定的功能。在编程中怎么定义变量,怎么给变量赋值,怎么使用控制程序来操作变量,都是初学者需要掌握的基础知识。下面演示一个用变量进行加法、减法的案例,效果如图1-2所示。

     

    1-3  取之不尽的箱子

    【实现过程】

    1)首先会提示用户输入自己的选择;

    2)根据用户的选择获取输入的值;

    3)开始做相应的加法或减法运算,当while条件满足时程序会重复执行,其中switch语句会根据输入的值来执行相应的代码。

    详细代码如下:

    #include <iostream>

    #include <iostream.h>

    int main()

    {

    int inputnum, outputnum,sum=0;

    int sel;

    do

    {

    cout<<"选择(1:输入箱子数据 2:取出箱子数据 3:查询库存箱子数量 其他退出):";

    cin>>sel; //取得输入选择

    switch(sel)

    {

    case 1:{

                 std::cout << "输入箱子数据:" << std::endl;

                 std::cin >>inputnum;  

     sum=sum+inputnum; //累加输入箱子数据

       };

     break;

    case 2:{

                     std::cout << "取出箱子数据:" << std::endl;

                 std::cin >>outputnum;  

     sum=sum-outputnum; //减去输入箱子数据

       };

     break;

    case 3:{

                   std::cout << "箱子数量为:" <<sum<< std::endl;

       };

     break;

    }

    }while(sel==1||sel==2||sel==3); //条件为真时重复执行

    return 0;

    }

    【代码解析】

    1)变量占用一定的内存空间,用来存储一个不确定的值。每一个变量都需要一个名字,以便与其他变量相区别,如inputnumoutputnumsum。变量是可以进行各种运算的,如sum=sum-outputnum;。程序中int是数据类型,C++的基本数据类型有很多,如charshort int (short)long int (long)等。程序中的sum=0;是变量初始化语句。

    2C++提供了一些控制结构语句来实现程序的执行顺序:

    选择结构:格式为if (condition) statement,其中,condition是一个将被计算的表达式;

    循环结构:while的格式为while(表达式expression){ 语句statement},它的功能是当expression的值为真时,重复执行statement

    多项选择结构:格式为switch (expression) {case constant1:block  1 break; default: default block of instructions},其中,default表示其他选择,也就是上面的条件都不符合时将执行此选择。

     

    提示:编写程序时,首先要定义变量并将其初始化,然后对变量进行处理,有时在程序最后需要释放为变量申请的内存。

    案例1-4物品存放

    【案例描述】

    实际编程中,经常将几个变量混合在一起进行计算,以实现for循环功能,下面的代码会帮助读者理解这种混合使用。本例演示的是如何打印一个正三角形符号,其中会涉及两种特殊的数据类型的前缀:本例有符号和无符号。本例效果如图1-3所示。

     

    1-4  物品存放

    【实现过程】

    1)子函数用来打印正三角图形,它定义了一个循环从第1行到输入行数n。循环中再嵌套两个循环,用来打印空白字符' '和打印'*'字符。代码如下:

    void trgl(unsigned int n)   //打印正三角形的函数

    {

    unsigned i,j;

    for (i=1;i<=n;i++) //根据输入行数执行1n的循环

    {

    for (j=1;j<(n-i+1);j++) //打印空白字符

    cout<<' ';

    for (j=1;j<=(2*i-1);j++) //打印*字符

    cout<<'*';

    cout<<endl;

    }

    }

    2)主程序提示用户输入要打印的行数,然后调用正三角函数进行输出。代码如下:

    void main()

    {

    unsigned int k; //定义整数

    cout<<"请输入要打印的行数:";

    cin>>k;

    trgl(k); //调用打印正三角的函数

    system("pause");

    }

    【代码解析】

    1unsigned int n和unsigned i,j;这两个变量定义有什么区别呢?整型数据类型(charshortlongint)可以是有符号的(signed)或无符号的(unsigned),这取决于需要表示的数据范围。有符号类型(signed)可以表示正数和负数,而无符号类型(unsigned)只能表示正数和0;程序中的变量默认都是signed。signed和unsigned 也可以被单独用来表示简单类型,意思分别与signed int和unsigned int相同。int数据类型的有符号范围为:-21474836482147483647,无符号范围为:04294967295

    2)控制结构for,可以循环执行里面的代码。如for (i=1;i<=n;i++)表示执行n次循环,n代表用户输入的行数。上面代码中的3for循环用来实现打印正三角的功能。

     

    提示:本例的光盘代码中有一个打印反三角形的函数re_trgl,读者可以编程调试一下,以了解它与打印正三角形函数的区别,重点是理解它们在算法上的差别。

    案例1-5交换物品

    【案例描述】

    变量定义为内存的一部分,用以存储一个确定的值。每一个变量需要一个名字,以便将它与其他变量相区别,变量的值是可以改变的。本例将演示如何赋值给变量,如何改变变量的值,效果如图1-4所示。

     

    1-5  交换物品

    【实现过程】

    1)提示用户输入任意两个数;

    2)程序使用if语句判断哪个数小,哪个数大;

    3)执行while循环,累加从小数到大数所有的值,最后输出结果。

    详细代码如下:

    #include <iostream>

    int main()

    {

         //提示输入数的范围

    int v1, v2, lower, upper;

    std::cout << "输入两个数: " << std::endl;

    std::cin >> v1 >> v2;

    //小数存储在lower变量中,大数存储在upper变量中

    if (v1 <= v2) {

    lower = v1;

    upper = v2;

    } else {

    lower = v2;

    upper = v1;

    }

    //求从小数到大数的累计和,包括小数和大数本身的值

    int sum = 0;

    int val = lower;

    while (val <= upper) //直到upper大于val

    {

    sum += val;   // sum = sum + val

    ++val;       // 累加 val (等同于val = val + 1)

    }

     

    std::cout << "累加从 " << lower << " " << upper

              << " 得到的值为: " << sum << std::endl;

        system("pause");

        return 0;            //返回语句

    }

    【代码解析】

    1if (v1 <= v2)是判断语句,如果输入的v2是大数,那么将v2的值赋给变量upper,小数赋给变量lower,否则相反。

    2)赋值运算符(=)的功能是将一个值赋给一个变量,如lower = v1;,表示变量lower的值为v1。cin用于输入,cout用于输出。cout(标准输出流)通常被定向到屏幕,而cin(标准输入流)通常被定向到键盘。

     

    提示:std::cin、std:: coutcincout的意义相同,不同的是cincout在使用时需要在程序开始处包含#include<iostrsam>,而如果没有这条语句,就要在程序中写明std::cin和std:: cout

    1.4  运算符与表达式

    案例1-6 单片机应用(位操作)

    【案例描述】

    在与硬件操作相关的编码过程中,经常需要通过改变内存中某单元的某一位(bit)来改变位值,这就是位运算。位运算是针对二进制的操作。本例讲解单片机开发中常见的位运算的使用情况,效果如图1-8所示。

     

    1-6  单片机应用

    【实现过程】

    程序定义了4short型变量,然后进行各种位运算,最后输出结果。代码如下:

    #include <iostream>

    using namespace std;

    int main()

    {

    using namespace std;

    short X=521,Y=123,C,D,E; //声明4short型变量

    C=X & Y; //位与

          D=X | Y; //位或

          E=~X ; //取反

    cout<<"C is "<<C<<endl; //输出

    cout<<"D is "<<D<<endl; //输出

    cout<<"E is "<<E<<endl; //输出

    system("pause");

    return 0;

    }

    【代码解析】

    C++6种位运算符:按位与(&)、按位或(|)、按位异或(^)、取反(~)、左移(<<)、右移(>>)。其中,按位与运算符(&)是双目运算符,其功能是将参与运算的两个数对应的二进位进行相与操作。只有对应的两个二进位均为1时,结果位才为1,否则为0。参与运算的数以补码方式出现。

     

    提示:单片机的操作涉及硬件时,需要程序员熟悉二进制知识。

    案例1-7 房间面积(*

    【案例描述】

    实际编程中经常要用到数学运算,所以后面的几个【案例会介绍在程序中如何正确使用算术运算符。本例计算房间面积和比较房间面积的大小,效果如图1-9所示。

     

    1-7  房间面积(*

    【实现过程】

    1)编写一个子函数,提示用户输入房间的长度和宽度,然后计算面积。如果用户输入的长度和宽度之和小于或等于0,给出错误提示,返回面积为0。其代码如下:

    double calcu_area(void)

    {

         double Length,Width,area; //定义房间的长度、宽度、面积

    cout<<"请输入房间的长度、宽度:";

    cin>>Length>>Width;          //取得长度、宽度

    if(Length+Width>0)

    {

         area=Length*Width; //房间的面积

    }

    else

    {

    cout<<"错误!输入的房间的长度、宽度不能构成房间!"<<endl;

    area=0;

    }

    return area;

    }

    2)主程序首先要定义用来存储房间面积的变量,再调用两次上面的子函数来计算房间1和房间2的面积,然后输出这两个面积;接着用判断语句比较两个面积的大小,显示面积的差值;最后计算两个房间面积的和。代码如下:

    void main()

    {

    double area1,area2; //定义存储两个房间面积的变量

    area1=calcu_area(); //调用计算函数

    area2=calcu_area();

    cout<<"房间1面积:"<<area1<<endl;

    cout<<"房间2面积:"<<area2<<endl;

    if(area1>area1)

    cout<<"房间1面积比房间2大:"<<area1-area2<<endl;

    else

        cout<<"房间2面积比房间1大:"<<area2-area1<<endl;

    cout<<"总面积:"<<area1+area2<<endl;

        system("pause");

        return ;

    }

    【代码解析】

    1)这个【案例介绍了算术运算符号在实际中的运用。C++语言支持的5种数学运算符为:加(+)、减(-)、乘(*)、除(/)、取模(%);计算房间的面积area=Length*Width;,输入的是double数据类型,返回的也是该类型。area1-area2表示两个double型变量相减。

    2calcu_area()是子函数,即用户可以自定义的函数;相同功能的代码写在一起,使代码的可读性更强、更精练。

     

    提示:加(+)、减(-)、乘(*)、除(/)运算对读者来说已经很熟悉了,它们和一般的数学运算符没有区别。读者不太熟悉的是【案例011用百分号(%)表示的取模运算(module)。

    案例1-8 平分物品(/

    【案例描述】

    本例还是数学计算的话题,是除运算符(/)在编程中的应用。本例还会提到怎样使用数组存储数据,这里利用循环控制结构,重复数学计算问题,另外,还介绍了怎样按格式输出对齐数据。本例效果如图1-10示。

    【实现过程】

    程序首先初始化各自所分到的量A[]0.0,然后循环10次,按比例分物品,最后输出分得的结果。其代码如下:

    #include <iostream.h>

    #include <iostream>

    #include <iomanip.h>

    int main()

    {

    float S,A[]={0.0,0.0,0.0};   //用数组存放各自所分到的量

    char *name[]={"老大分得","老二分得","老三分得"};

    for(int i=1;i<=10;i++)

    {

    S=19-A[0]-A[1]-A[2]; //进行计算

    // cout<<S<<endl;

    A[0]=S/2+A[0];

    A[1]=S/4+A[1];

    A[2]=S/5+A[2];

    // cout<<A[0]+A[1]+A[2]<<endl;

    }

    for(int j=0;j<3;j++)

    cout<<setiosflags(ios::left)

    <<setw(4)<<name[j] //设置输出方式

    <<resetiosflags(ios::left)

    <<setw(10)<<setiosflags(ios::fixed)<<setprecision(10)<<A[j]<<endl;

       system("pause");

       return 0; //返回语句

    }

    【代码解析】

    1A[0]=S/2+A[0];是一个算术表达式,计算顺序是先S/2,再+A[0],/符号为除运算符。

    2setiosflags是命名空间iomanip里面的C++操作符,作用是执行(ios::left)指定的动作。resetiosflags(ios::left)表示输出左对齐,setiosflags(ios::fixed)<<setprecision(10)表示输出小数点后10位的浮点数。setw(4)表示输出的域宽度是4

     

    提示:实际编程中应灵活使用setw()setprecision()对齐输出结果。

    案例1-9 取余数(%

    【案例描述】

    百分号(%)表示取模运算(module),即取两个整数相除的余数。如果a = 11 % 2;,变量a的值将会为1,因为111除以2的余数。本例判断用户输入整数能否被357整除,效果如图1-11所示。

     

    1-9  取余数(%

    【实现过程】

    程序判断用户输入的一个整数能否被357整除,最多只能输入3次。代码如下:

    #include <iostream.h>

    #include <iostream>

    int main()

    {

    int x,j=0;

    do

    {

    cout<<"Input a number:"; //提示输入一个整数

    cin>>x;

    if ((x%3==0) && (x%5==0) && (x%7==0)) //不能被357整除

    cout<<x<<" can be devided by 3,5 and 7."<<endl;

    else

    if (x%3==0)

     if (x%5==0)

    cout<<x<<" can be devided by 3 and 5."<<endl;     //能被35整除

      else

    if (x%7==0)

    cout<<x<<" can be devided by 3 and 7."<<endl; //能被37整除

    else

    cout<<x<<" can be devided by 3."<<endl;

    else

     if (x%5==0)

    if (x%7==0)

    cout<<x<<" can be devided by 5 and 7."<<endl; //能被57整除

    else

    cout<<x<<" can be devided by 5."<<endl; //能被5整除

     else

    if (x%7==0)

    cout<<x<<" can be devided by 7."<<endl; //能被7整除

    else

    cout<<x<<" cann't be devided by 3,5 or 7."<<endl;

    //不能被357整除

       j++;

       }while (j<=2);

      system("pause");

      return 0; //返回语句

    }

    【代码解析】

    1)注意代码j=0;和if (x%3==0),运算符=(赋值号)不同于运算符==(逻辑等)。第一个(=)是赋值运算符(将等号右边的表达式值赋给左边的变量);第二个(==)是一个判断相等关系的运算符,用来判断运算符两边的表达式是否相等。

    2do{ ...}while (j<=2);是重复循环结构,先执行{...}中的代码,然后才检查条件(j<=2),条件不满足时结束循环。

     

    提示:编程时双等号(==)和单等号(=)不能混淆;do-while循环和while循环的区别是前者多执行其中的代码一次,然后判断是否结束循环。

    案例1-10 反转整数(%

    【案例描述】

    本章开始举算法的是例。算法是指解题方案准确而完整的描述,是一系列解决问题清晰的指令。算法代表用系统的方法描述解决问题的策略机制。也就是能够对一定规范的输入,在有限时间内获得所要求的输出。本【案例举取得输入整数中每个整数位数的值,通过反向组合输出整数。本例效果如图13-1所示。

    【实现过程】

    取得一个输入的整数,通过取模运算取得每个整数位数的值,通过反向组合输出整数。代码如下:

    int main()

    {    

    int i,num,subscript; //定义变量

    int digit[5]; //暂存整数各位数的值  

    cout<<"输入一个整数:"<<endl;    

    cin>>num;    

    cout<<"原来整数为:"<<num<<endl; //取得输入整数

    subscript=0; //数组下标初值    

    do{        

    digit[subscript]=num%10; //取模运算     

    num=num/10;        

    subscript++; //修改下标    

    }

    while (num>0);    

    for(i=0;i<subscript;i++) //整数的反向组合        

    num=num*10+digit[i]; //原来的值乘10再加各位数的值

    cout<<"反向后整数为:"<<num<<endl;     

    system("pause");

    return 0;

    }

    【代码解析】

    代码中%表示取模运算,取模运算是取两个整数相除的余数。int digit[5];,声明digit数组是由5int型元素组成。

     

    提示:不同的算法可用不同的时间、空间或效率来完成同样的任务。一个算法的优劣可以用空间复杂度与时间复杂度来衡量。

    案例1-11求余下的物品数(%

    【案例描述】

    %”表示取模运算,案例011也提到过,取模运算是取两个整数相除的余数,例如,代码a = 17 % 3;,变量a的值将会为2,因为217除以3的余数。有了这个运算符,在编程中方便多了,下面举一个分配物品的【案例,效果如图2-8所示。

     

    1-11  求余下的物品数(%

    【实现过程】

    1)定义两个子程序surp和aver,函数surp()输入物品数量,计算物品数量和参加物品分配人数取模运算a%num,如有余数,则输出每人分得的物品int(a/num)和余下的物品a-num* (int(a/num)),否则调用函数aver()输出物品,平均分配每人分得的物品a/num。其代码如下:

    void surp (int a);

    void aver (int a);

    int num=6;

    void surp (int a) {

    if ((a%num)!=0)           //不能平均分配,那有余下的物品

      cout << "物品每人分得:"<<int(a/num)<<"余下的物品:"<<a-num*(int(a/num))<<endl;

    else

      aver (a);

    }

    void aver (int a) { //刚好平分

    if ((a%num)==0) //取模运算

      cout <<" 物品平均分配,每人分得物品:"<<a/num<<endl;

    else surp (a);

    }

    2)主程序显示参加物品分配人数,一个while循环,提示输入物品数量,取得物品数量,调用上面的子程序surp,看能否平均分,如不能平均分,还余下多少件。代码如下:

    int main () {

    int i;

    cout <<"参加物品分配人数:"<<num<<endl;

    do {

      cout << "输入物品数量: (0 to exit)";

      cin >> i;

      surp (i);

    } while (i!=0);              //如果输入为0,则退出主程序

    return 0;

    }

    【代码解析】

    1)这个【案例演示算术运算符在实际中的运用。代码中的a%num是物品数量和参加物品分配人数的取模运算;(a%num)!=0时为有余数,否则(a%num)==0为没有余数,平均分配完。

    2)代码if ((a%num)!=0)使用了if控制结构,格式是:if (condition) statement1 else statement2,对表达式(a%num)!=0进行判断,为真则执行statement1,否则执行statement2

     

    提示:学习过Visual Basic语言的读者应该知道,其中有个mod运算符,或者说求余运算,与C++%取模运算是一样的。

    案例1-12 班级人数(综合)

    【案例描述】

    前面的案例较详细地介绍了变量和算术运算符。在编程中,综合利用它们可以实现一些具体功能。本例是一个统计班级成绩的程序,通过计算和判断,得到优秀、及格或不及格的各成绩段的人数。本例效果如图1-12所示。

    【实现过程】

    1)该子函数输入3门课程的成绩,然后计算平均分,判断是优秀、及格或不及格,返回对应优秀、及格或不及格的人数。其代码如下:

    int& level(int grade[],int size,int& tA,int& tB,int& tC)

    {

    int temp=0;

    for(int i=0;i<size;i++) //循环取得成绩总分

    temp+=grade[i]; //累加

    temp/=size; //取得成绩平均分

    if (temp>=85)  return tA; //优秀

    else  if (temp>=60) return tB; //及格

    else return tC; //不及格

    }

    2)主函数中,首先定义数组int a[5][3]用来存储成绩,然后初始化优秀、及格或不及格人数,初始化学生人数和3门课程,再循环输入每个学生的成绩,调用上述子函数进行计算,最后显示循环输入每个学生的成绩。代码如下:

    int main()

    {

    int a[5][3]={{65,60,78},{60,80,90},{89,92,78},{75,65,82},{65,50,58}};//学生成绩

    int typeA=0,typeB=0,typeC=0;

    int stdSize=5;   //假设统计5个学生的成绩

    int gradeSize=3;   //3门课程

    for(int i=0;i<stdSize;i++)   //循环输入每个学生的成绩

    level(&a[i][0],gradeSize,typeA,typeB,typeC)++;//返回的引用作为左值,直接增量

      cout<<"优秀人数为:"<<typeA<<endl;

    cout<<"及格人数为:"<<typeB<<endl;

        cout<<"不及格人数为:"<<typeC<<endl;

        system("pause");

        return 0;                                                 //返回语句

    }

    【代码解析】

    1a[5][3]是二维数组,大小为5´3。函数level()的声明中,有几个变量的类型后面跟了一个地址符&,它的作用是指明变量是按地址传递的,而不是像通常一样按数值传递。

    2)组合运算符(+=-=*=/=及其他),这些组合运算符让代码看起来更简洁。本例应用了组合运算符+=/=temp+=grade[i];等同于temp=temp+grade[i];。同样,temp/=size;等同于tempt=temp/size。

     

    提示:实际编程中应灵活应用组合运算符,使编写的程序更简洁。

    案例1-13 称水果(比较)

    【案例描述】

    在编程的时候经常会用到判断语句if和多选项判断语句switch。本例通过判断输入水果的品种得到不同的水果价格,然后计算价钱。通过本例的介绍,读者应学会如何正确使用if和switch。本例效果如图1-13所示。

     

    1-13  称水果(比较)

    【实现过程】

    程序首先提示用户输入水果的重量、水果的品种编号,然后取得品种编号对应的价格,做后计算应付的钱数并输出这个价钱。其代码如下:

    #include <iostream>

    #include <iostream.h>

    #include <math.h>

    int main()

    {

    int type,sum=0;

    double sale,weigt=0,price;

        std::cout << "输入水果重量:" << std::endl;

        std::cin >>weigt;  

       std::cout << "1.西瓜1.80 2.木瓜1.70 3.哈密瓜2.60 4.山竹1.60" << std::endl;

        std::cout << "输入水果品种编号:" << std::endl;

        std::cin >>type;  

    switch (type) //输出数字

    {

    case 1:       //西瓜

                std::cout << "输入水果品种---西瓜,每公斤1.80" << std::endl;

          price=1.80;

    break;

    case 2:

                std::cout << "输入水果品种---木瓜,每公斤1.70" << std::endl;

    price=1.70;

    break;

    case 3:

                std::cout << "输入水果品种---哈密瓜,每公斤2.60" << std::endl;

        price=2.60;

    break;

    case 4:

                std::cout << "输入水果品种---山竹,每公斤1.60" << std::endl;

    price=1.60;

    break;

    default:

    break;

    }

          if (weigt<=0)

           sale=0.00;

       else

               sale=floor(weigt*price*100 +0.5)/100; //小数点后2位四舍五入

       cout<<"应付人民币: "<<sale<<""<<endl;

       system("pause");

       return 0;

    }

    【代码解析】

    1)代码中的if为条件结构,形式是if (condition) statement,条件结构表示仅在某种条件满足的情况下才执行一条语句或一个语句块。代码中,switch语句的语法形式为switch(expression) {[case constant1:]. [statement]. . . [default:statement] },它的作用是检查表达式expression是否有多个可能的常量值,如值为constant1时,执行statement,作用类似于多个if语句的嵌套。

    2)数学函数floor返回小于或者等于指定表达式的最大整数,使用它需要包含头文件math.h

     

    提示:从实现结果来看,switch是可以用if替换的,不过程序的可读性会变差,不如使用switch简洁。

    案例1-14 信号灯(++ --

    【案例描述】

    为了书写简练,使用算术运算符通常会改变写法,如递增运算符(++)和递减运算符(--)。本例以模拟常见的信号灯为例,介绍这两个符号的应用,效果如图1-14所示。

     

    1-14  信号灯(++ --

    【实现过程】

    程序先输入一个计算时间的整数,然后从0num循环,红灯变量递增,绿灯变量递减,时间间隔是1秒。代码如下:

    #include <iostream.h>

    #include <iostream>

    void main()

    {

    int red,green,num;

    cout<<"请输入一个数!"<<endl;

        cin>>num;

    if ((num>0)&&(num<60))

    {

             green=num;

    red=0;

    for(int i=0;i<num;i++)     //0num循环

    {

        system("cls");        //清除屏幕

    red++;            //red递增

        cout<<"红灯 "<<red<<endl;

    green--;     //green递减

        cout<<"绿灯 "<<green<<endl;

    _sleep(1*1000);     //延时1

    }

    }

    else cout<<"输入的数不合规定!"<<endl;

    system("pause");

    }

    【代码解析】

    1)递增运算符(++)和递减运算符(--)使得变量中存储的值加1或减1,分别等同于+=1-=1。因此,red++red+=1red= red +1在功能上全部等同,即全部使变量red的值加1。它的存在是因为最早的C编译器的原因,现在编译器已基本自动实行代码优化,所以,以上三种不同的表达方式编译成的机器代码在实际运行上已基本相同。同理,green--green-=1green= green -1在功能上全部等同,即使变量green的值减1

    2)表达式green =3;red=++green;,这时red的值为 4green的值为4,但表达式green =3;red=green++;,这时red的值为3green的值为4。虽然在简单表达式如a++或++a中,这两种代码的写法代表同样的意思,但当递增或递减的运算结果被直接用在其他的运算式中时,它们就代表不同的意思:当递增运算符被用作前缀prefix (++green)时,变量a的值先增加,然后再计算整个表达式的值,因此增加后的值被用在表达式的计算中;当它被用作后缀suffix (green ++)时,变量a的值在表达式计算后才增加,因此,a在增加前所存储的值被用在表达式的计算中。

     

    提示:初学者不一定都要求做到代码书写简练,要多实践。

    案例1-15 篮球反弹

    【案例描述】

    现实生活中小的数学计算问题要用编程实现,往往会涉及到循环和四则运行。虽然简单,但是编写程序要计算正确编程也要仔细。本【案例演示一个球从100m高处自由落下,每次着地后又跳回到原高度的一半再落下,再反弹,求它在第十次落地高度和经过的距离。本例效果如图1-15所示。

    【实现过程】

    第十次落地时,经过的距离为多少米,第十次反弹多高。其代码如下:

    #include <iostream.h>

    void main()

    {    

    double height=100,sum=0;    //定义高度和经过的距离

    int count = 10;

    int i;    

    for(i=1;i<=10;i++)     //10次反弹

    {

    sum +=height+height/2; //总的距离

    height /=2;

    }    

    cout<<"第十次落地时,经过"<<(sum-height)<<""<<endl;    

    cout<<"第十次反弹高度 :"<<height<<endl;

    system("pause");            //暂停

    }

    【代码解析】

    第一次落地走了100米,之后弹上去,有上有下,是一个2倍的进程。第一次是100,第二次是100*0.5^1*2,第十次是100*0.5^9*2,然后求和100+100(0.5^1+0.5^2+...+0.5^9)*2。

     

    提示:C++以书写简练著称的一大特色就是运用了组合运算符(+=-=*= /=及其他),这些运算符使得只用一个基本运算符就可改写变量的值。

    案例1-16 求被3整除的数(%+算法)

    【案例描述】

    在第1章学过数学计算符(*)、(/)和(%),本例涉及(%)运行。本例举个编程中经常用到的数学计算的【案例,效果如图1-16所示。

     

    1-16  求被3整除的数(%+算法)

    【实现过程】

    通过一个for循环,和3取模运行,逐个输出整数的值。其代码如下:

    #include <iostream>

    using std::endl;

    using std::cout;

    int main()

    {

         long i;

    int n=0;

    cout<<"个位数是6且能被3整除的数如下:"<<endl;

    for(i=10006;i<99996;i+10) //for循环

    {

     if(i%3==0)  //取模运算

    {

    cout<<i<<"\t";           //输出整数

    n++;

    }

    }

    cout<<"个位数是6且能被3整除的总个数如下:"<<n<<endl;

        return 0;

    }

    【代码解析】

    %表示的是取模运算,也就是取两个整数相除的余数。例如编写a = 11 % 3;,变量a的值将会为结果2,因为211除于3的余数。

    案例1-17 鸡兔同笼 

    【案例描述】

    这个案例是个古老数学问题,实际上是一个算术运算求解问题。涉及到算术运算符号,编程的时候经常会遇到这样简单的数学问题,本例根据笼中鸡兔头和脚数,求鸡兔头数,效果如图1-17所示。

    【实现过程】

    主函数输入测试次数,for循环取得头和脚的数量,判断是否有解,根据算法输出兔子和鸡的数量。代码实现如下:

    #include <iostream>

    using namespace std;

    int main()

    {

    int n;

    cout<<"输入测试数:"<<endl;

    cin>>n; //输入测试次数

    for(int i=1;i<=n;i++)

    {

    int m,n;

    cout<<"输入头和脚的数量:"<<endl;

    cin>>n>>m;

    if((m%2!=0)||((m/2-n)<0)||((2*n-m/2)<0)) //判断是否有解

    cout<<"No answer"<<endl;           //不能得出结果

    else

    cout<<"兔子数"<<2*n-m/2<<" "<<"鸡数"<<m/2-n<<endl; //根据算法

    }

    system("pause");

    return 0;

    }

    【代码解析】

    1)鸡兔同笼是中国古代著名趣题之一。有若干只鸡兔同在一个笼子里,从上面数有35个头,从下面数有94只脚,问笼中各有几只鸡和兔?这个问题有个最简单的算法:(总脚数-总头数*鸡的脚数)÷(兔的脚数-鸡的脚数)=兔的只数,按这个算法答案就是(9435×2)÷2=12(兔子数),总头数(35)-兔子数(12=鸡数(23)。

    2)代码基本上用的是简单数学运算符,涉及到C++基本的加、减、乘、除和取模算术运算。

    1.5  本章练习

     

     

     

    展开全文
  • 2014年除了响应式设计受到追捧外,侧边栏菜单也将是一个新趋势!...下边为大家献上侧边栏菜单创意案例展示 Parrot 在线地址:http://www.parrot.com/flowerpower/en#homepage 一个简洁的充满有机氛围的网站。
  • 以用户为中心的前端设计案例剖析

    千次阅读 2014-06-04 09:38:00
    以用户为中心的前端设计案例剖析 WEB前端设计的最终目标是打造用户喜爱的产品,属于产品设计的范畴。所以一切设计要以用户为出发点,追求最佳的用户体验。 一、banner的设计 在WEB项目设计中,我们经常会有...
  • Android案例:商品展示

    千次阅读 2017-04-25 14:34:15
    1、案例: 开发一个购物车,将购物车中的...(1)、只给出主要代码,界面设计不再给出。 (2)、创建数据库: 新建MyHelper类继承自SQLiteOpenHelper,SQLiteOpenHelper是一个抽象类,他有两个抽象方法,分别是onCr
  • 版式设计之DM应用案例分析与讲解

    千次阅读 2016-07-11 14:20:09
    内容提要:版式设计之DM应用案例: 食品、教育、房产DM版式设计分析与讲解以及8大突出主题的版式设计技巧分享.对PS感兴趣的朋友可加ps学习交流群:142574315  大家好,我是大家的好朋友谢梦,在之前的文章,我们...
  • 随着Material Design的流行,卡片式设计几乎成为当前界面设计的主流模式,并且已经深入到各个行业、领域的UI设计当中。...而卡片作为容器,能够适应不同屏幕大小而不破坏信息的展示效果。 智能化的生活...
  • 高德智慧交通地图空间可视化SDK设计与实现

    万次阅读 多人点赞 2021-02-01 17:59:59
    一、背景地图空间可视化作为高德智慧交通前端业务中最重要的功能之一,承担着城市交通大脑、全境智能大屏等业务中大量的地图渲染需求。作为向用户展示交通数据的窗口,我们需要展现省、市、区、商圈、...
  • 电商平台系统架构设计案例分析

    千次阅读 2016-09-21 13:27:00
    电商平台系统架构设计案例分析 - 苏宁易购商品详情设计 2016-03-21 09:39:51 商品详情系统介绍 基本介绍 商品详情系统是一个展示商品基本信息、参数等详情的系统,是商品购买的入口。它是电商平台中访问量最大...
  • 面对纷繁复杂的网页内容,用户通过查询关键词表达需求,期望在响应的查询...如网页设计、应用程序设计一样,搜索框设计同样是一门值得研究的学问。 搜索框对于网站或应用程序有着非常重要的作用。一个好的搜索框...
  • 云升科学园,广州科学城第四代产业综合体,集“展示、研发、运动、商业、休闲、居住、孵化、办公”为一体的多功能产业园区,20万㎡产业空间、2万㎡产业配套,由专属商务管家统一运营。近2500平米标准层、约80%高使用...
  • 11个优秀的交互设计作品集

    万次阅读 2019-10-13 16:40:30
    不管你做什么样的设计,一个在线交互设计作品集都是必备的。在大多数情况下,公司和客户根本不会考虑你是做什么工作的。虽然很多交互设计师可能认为自己的工作不能很好地以视觉化的方式呈现出来,但一个作品集对于...
  • 数据仓库建模案例

    千次阅读 2019-09-03 11:05:38
    前面介绍的两种维度建模方法都是多维表对应单事实表,但在很多时候维度空间内的事实表不止一个,而一个维表也可能被多个事实表用到。在业务发展后期,绝大部分维度建模都采用的是星座模式。 4. 三种模式对比 ...
  • 网页设计师们经常会浏览高质量的网页设计作品分享网站,来解决灵感枯竭的问题。因为在这些优秀的网站设计实例中可以看到某些趋势,诸如:扁平化设计、视差滚动、响应式设计、流行配色设计等等。今天小摹为大家带来10...
  • ZooKeeper实际应用案例-开发实战

    万次阅读 多人点赞 2018-12-18 14:14:51
    检索服务器,代码设计完全采用官方案例,所以详细的代码解读请参考上面提到的两篇文章,这里只做下简述。 代码有两个类DataMonitor和LoadSaidsExecutor。LoadSaidsExecutor是启动入口,他来启动DataMonitor监控...
  • 对于设计师来说,一个好的个人网站可以很好的为自己展示UI/UX设计作品集;对于企业来说,一个好的网站可以为自己持续带来流量以及品牌曝光;网页设计师们总会热衷于追随最新的设计趋势,因为在这些优秀的网站设计...
  • 最优秀的数据可视化案例欣赏

    万次阅读 2019-03-20 07:46:52
    优秀的可视化案例欣赏美国运通漏斗图能量预测Bullseye雷达预算图城市统计数据可视化实时游戏数据能量监测自定义分析电力图表如何可视化您的数据? 数据是新的生产资料,如果您无法以直观的方式显示数据,那么它基本...
  • 大屏数据可视化设计指南

    万次阅读 多人点赞 2019-01-03 14:25:31
    把相对复杂、抽象的数据通过可视的方式以人们更易理解的形式展示出来的一系列手段叫做数据可视化,数据可视化是为了更形象地表达数据内在的信息和规律,促进数据信息的传播和应用。 在当前新技术支持下,数据可视化...
  • 商品详情系统是一个展示商品基本信息、参数等详情的系统,是商品购买的入口。它是电商平台中访问量最大的系统之一,苏宁易购大促期间PV量和UV量很大,这么大的访问量对系统的并发能力要求高。在业务上它与周边系统的...
  • 设计出来的原型产品就是你项目组后期整个项目所要做成的样子,只是为了向客户展示。注意啦!这款工具所设计出来的产品项目原型作用只有一个,那就是向客户展示你对他要做的项目~~ 好了,话不多说,来简单的设...
  • 如何根据技术方案做案例设计

    千次阅读 2018-03-18 16:30:55
    对于一些测试新人来讲,很容易存在一个误区,看懂需求...结合一个工作中的实例,来源于实习生负责的一个小功能,来分析如何根据开发原理进行测试设计。 功能:在APP的首页有一个广告图片,称之为banner,实现了ba...
  • MySQL数据库——案例:电子商务网站设计

    千次阅读 多人点赞 2020-05-22 23:55:48
    在了解数据库设计的基本流程、规范以后,为了学以致用,本节将以电子商务网站为例,演示如何进行数据库设计。通过实际操作,加深对数据库设计的理解。 1、需求分析 电子商务网站是一种在互联网上开展电子商务的平台...
  • Verilog设计实例(7)基于Verilog的数字电子钟设计

    万次阅读 多人点赞 2020-06-17 21:51:43
    基于模块化的设计思想, 采用 Verilog HDL 语言设计一个能进行时、分、秒计时的二十四小时制的数字电子钟, 并具有整点报时功能。 采用数码管进行时间显示,要求显示格式为:小时-分钟-秒钟。
  • 欧式空间到双曲空间

    千次阅读 2019-06-11 21:53:00
    目前为止,大多数表示学习方法停留在了欧式空间(Euclidean Space),但事实上除了简单的欧式空间,还存在很多其他的非欧式空间可以用来构建AI模型,并且我们也发现了近两年越来越多的学者也开始在这个领域做出了...
  • 全球顶级的5个数据可视化案例

    万次阅读 2018-09-06 09:45:15
    全球顶级的5个数据可视化案例 摘要: 当数据进行可视化后,就有了参考点,可以用各种合适的方法重新处理数据或进行测试,这能使企业能够更快、更平稳、更有效地发展。这也可以让它们在利益和名声受损之前排除...
  • 大师级别的UE设计细节

    万次阅读 多人点赞 2013-12-03 11:26:31
    当用户第一次浏览一个网站或使用一个APP时,前5秒异常重要,使用者是关闭它还是...正如Jesse James Garrett所说:“在进行系统设计时,提供优质的用户体验是一种重要的、可持续的竞争优势,往往决定产品的市场走向。”

空空如也

空空如也

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

展示空间设计案例

友情链接: sky_stck.rar