int x = 0;
char *pstr = NULL;
Bool bOpened = FALSE;
char Name[100] = {0};
char *szName = “hello world”;
char szName[] = “hello world”;
char szName[100] = “hello world”;
UNICODE_STRING ustrName = {0};
CString szName = _T(“”);
memset(buffer, 0, 1024);
1.边界条件是指软件计划的操作界限所在的边缘条件。数据类型:数值、字符、位置、数量、速度、地址、尺寸等,都会包含确定的边界。应考虑的特征:第一个/最后一个、开始/完成、空/满、最慢/最快、相邻/最远、最小值/最大值、超过/在内、最短/最长、最早/最迟、最高/最低。这些都是可能出现的边界条件。边界条件测试通常是对最大值简单加1或者很小的数和对最小值减少1或者很小的数,例如:
l 第一个减1/最后一个加1;
l 开始减1/完成加1;
l 空了再减/满了再加;
l 慢上加慢/快上加快;
l 最大数加1/最小数减1;
l 最小值减1/最大值加1;
l 刚好超过/刚好在内;
l 短了再短/长了再长;
l 早了更早/晚了更晚;
l 最高加1/最低减1。
另一些该注意的输入:默认,空白,空值,零值和无;非法,错误,不正确和垃圾数据。根据边界来选择等价分配中包含的数据。然而,仅仅测试边界线上的数据点往往不够充分。提出边界条件时,一定要测试临近边界的合法数据,即测试最后一个可能合法的数据,以及刚超过边界的非法数据。
2.默认值测试(默认、空白、空值、零值和无)
好的软件会处理这种情况,常用的方法:一是将输入内容默认为合法边界内的最小值,或者合法区间内某个合理值;二是返回错误提示信息。
这些值在软件中通常需要进行特殊处理。因此应当建立单独的等价区间。在这种默认下,如果用户输入0或-1作为非法值,就可以执行不同的软件处理过程。
3.破坏测试(非法、错误、不正确和垃圾数据)
数据测试的这一类型是失败测试的对象。这类测试没有实际规则,只是设法破坏软件。不按软件的要求行事,发挥创造力吧。
举一个例子:实现C库中的memmove函数。
很多程序员可能会很快写出如下程序:
void memcpy(void *pDst,const void *pSrc, size_t size)
{
assert(pDst != NULL);
assert(pSrc != NULL);
char *pstrSrc= (char *)pSrc ;
char *pstrDst = (char *)pDst ;
/* 从起始部正向拷贝 */
while(size--)
*pstrDst++ =
*pstrSrc++;
}
但是,这里忽略了一个很重要的情况就是:pDst和pSrc指定的内存有可能是重叠的。如果按照这样拷贝的话,那么如果(参见下图第2种情况):
(pSrc<pDst) && ((char*)pSrc+size > pDst)
那么,按照上面的程序代码拷贝,将会把pSrc尾巴部分的数据覆盖污染了。
void memcpy(void *pDst,const void *pSrc, size_t size)
{
assert(pDst != NULL);
assert(pSrc != NULL);
/* pSrc与pDst共享同一块内存区域 */
if((pSrc<pDst) && ((char*)pSrc+size >
pDst))
{
char *pstrSrc=
(char *)pSrc + size -1;
char *pstrDst =
(char *)pDst + size -1;
/ *
从尾部逆向拷贝 */
while(size--)
*pstrDst -- = *pstrSrc--;
}
else
{
char *pstrSrc=
(char *)pSrc ;
char *pstrDst =
(char *)pDst ;
/*
从起始部正向拷贝 */
while(size--)
*pstrDst++ =
*pstrSrc++;
}
}
出错处理的方式方式很多,也很灵活。只要不遗漏对错误的处理就行。现在介绍两种错误处理方式:
1) goto error方式
语句goto是大家不大喜欢使用的,也是程序设计专家建议不要使用的一句话。因此goto语句似乎就要退出历史的舞台。但幸好它找到了一个用武之地,那就是用goto error方式来处理出错。下面是一个例子:
NTSTATUS QueryObjectName(HANDLE h)
{
int
ret;
NTSTATUS
st;
char
*str = "Hello, how are u?";
char
*pstr = (char *)malloc(256);
if (pstr == NULL)
{
printf("No more memory\n");
goto Error;
}
strncpy(pstr, str,
len);
pstr[len+1] =
'\0';
char *namebuf = (char
*)malloc(1024);
if (buf == NULL)
{
printf("No more memory\n");
goto Error;
}
st = NtQueryObject(h,
FileNameInformation, namebuf, ret, NULL);
if (!NT_SUCCESS(st))
{
printf("No more memory\n");
goto Error;
}
...
Error:
if (buf)
{
free(buf);
}
if (pstr)
{
free(pstr);
}
return st;
}
goto error方式把错误进行了集中处理,防止了在分支复杂的程序中错误情况的遗漏。
2) SHE:__try__leave__exception方式
SEH( structured exception handling ),即结构化异常处理方式,它是Windows系统独有的异常处理模型,主要由try-except语句来完成,与标准的try-catch相似。C++异常处理模型使用catch关键字来定义异常处理模块,而SEH是采用except关键字来定义;catch关键字后面像接受一个函数参数一样,可以是各种类型的异常数据对象,except关键字则不同,它后面跟的是一个表达式。
NTSTATUS QueryObjectName(HANDLE h)
{
int
ret;
NTSTATUS
st;
__try
{
char
*str = "Hello, how are u?";
char
*pstr = (char *)malloc(256);
if (pstr == NULL)
{
printf("No more memory\n");
__leave;
}
strncpy(pstr, str,
len);
pstr[strlen(str)] =
'\0';
char *namebuf = (char
*)malloc(1024);
if (buf == NULL)
{
printf("No more memory\n");
__leave;
}
st = NtQueryObject(h,
FileNameInformation, namebuf, ret, NULL);
if (!NT_SUCCESS(st))
{
printf("NtQueryObject failed\n");
__leave;
}
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
//获得异常代码
st = GetExceptionCode();
}
if (buf)
{
free(buf);
}
if (pstr)
{
free(pstr);
}
return st;
}
void reverse_str(char* str, size_t len)
{
if (NULL
== str || '\0' == *str || 0==len || 1==len)
{
return;
}
char
*p1=str;
char
*p2=str+len-1;
while(p1<p2)
{
char c = *p1;
*p1 = *p2;
*p2 = c;
p1++;
p2--;
}
}
//算法2,分配一个额外的内存,把字符串从后往前拷贝到内存中
char *reverse_str(char* str, size_t len)
{
if (NULL
== str || '\0' == *str || 0==len || 1==len)
{
return;
}
char
*buf=malloc(len+1);
while(len--)
{
*p1++=*p2--;
}
}
一个再复杂的算法程序,都是由循环,条件,顺序三种语句构成的(switch语句也可以转化为条件语句)。而这三种语句中,循环语句的使用是其中的关键。循环语句用来遍历和操作对应的数据结构,在遍历的过程中,完成对数据的处理和问题的解决。前几节中,几乎每个算法都用到了循环语句。以循环语句为核心,循环语句把其他语句联系起来。因此,算法中,最重要的地方就是对循环的熟练掌握与使用。
下面把常用的各种数据结构的循环语句列出,方便大家在编程的时候使用:
1.数组
for (int i = 0; i < n; i++)
{
printf(“%d\n”, a[i]);
}
2.字符串
while (*str !=’\
{
str++;
}
3.链表
while (p)
{
p = p->next;
}
4.栈空/栈满/队空/队满
while(栈非空)
{
出栈;
操作出栈元素
}
5.指针大小比较
while (pStart < pEnd)
{
pStart++;
}
6.数为正
while (size--)
{
}
do
{
}while(size--)
7.反复循环直到条件成熟
while(1)
{
if (something is true)
break;
}
熟练各种循环语句的写法,为实现各种复杂的算法奠定了基础。因此,要在各种循环语句上下功夫,达到熟练书写,灵活运用的程度。
对于一些复杂的算法问题,还可以通过递归的思想来解决。有的问题,用传统的方法来解决,显得很复杂。这个时候,可以考虑是不是可以用递归来解决。因为用递归的思想解决问题,既简单,又清晰,往往让看似复杂的问题得到简单解决的可能。
笔者曾经在面试微软的时候遇到考官考查了一道树的结点和它的兄弟之间的关系问题。开始我试着用队列结合树的遍历去解决这个问题,但是发现如果那样去解决会显得很麻烦,最后能否行得通也还是一个问题。而当时面试官也提醒我,一旦确定了思路,就不能再改变。于是我发现原来的想法显得很复杂,还有可能陷入死胡同。
于是我决定放弃原来的思路,重新分析问题。经过我的重新分析,结合以往的经验,对于树的问题,几乎都可以考虑用递归的方法去实现,笔者按照递归的方法,首先分析了这个问题最简单的情况(即递归的出口),然后再对复杂的情况利用递归方法调用它自身。思路越来越清晰,我便开始动手写在纸上写起代码来。没过多久,代码就完成了,并交给了面试官。面试官看完代码后,很满意的点点头。问题最终得到圆满的解决。
在利用递归解决问题的时候,有2个关键的地方:首先就是要找到递归的出口,也就是问题的最简子问题,也就是问题中最简单的情况,比如对于一个链表,最简单情况就是链表为空或者只有一个结点;对于一个字符串,最简单情况就是字符串为NULL或者只有一个’\
其次也是最关键的是要通过分析和转换,将原问题转化为子问题,也就是推导出问题的递归式。子问题和原问题是同类问题,但子问题的规模应该比原问题要小。比如求n!那么它的子问题就是(n-1)!,只需要把(n-1)!乘以n就是n的阶乘了。又比如要求斐波那契数列的第n个值,只需要求出(n-1)的值和(n-2)的值,那么就可以求出n的值了。在先序遍历树t的时候,当把根节点遍历完后,因为t->left和t->right是它的子树,也就是子问题,所以然后用递归遍历t->left和t->right就可以了。
题目:用一条语句,不声明临时变量,实现strlen来计算一个字符串的长度。
如strlen(“hello world”)=11
size_t strlen (const char * str )//常规方法
{
const char *eos = str;
while(
*eos++ ) ;
return( eos - str - 1 );
}
size_t strlen(const char *str)//递归方法
{
return
(str==NULL || *str==’\
}
题目:将一个字符串逆置。
比如:”hello world”-->”dlrow olleh”
void reverse_str(char* str, unsigned int len)//常规方法
{
if (NULL
== str)
{
return;
}
char
*p1=str;
char
*p2=str+len-1;
while(p1<p2)
{
char c = *p1;
*p1 = *p2;
*p2 = c;
p1++;
p2--;
}
}
void reverse_str(char *str, size_t len)//递归方法
{
if(len==0 || len==1 || str == NULL || *str=='\0')
{
return;
}
char tmp
= str[0];
str[0]=str[len-1];
str[len-1]=tmp;
reverse_str(str+1, len-2);
}
题目1:计算一个链表的中间节点。
思路:定义2个指针,一个跑2步,一个跑1步,当跑2步的到链表终点,跑1步的正好到达中间结点
node * find_list_middle_node(node * list)
{
if(list == NULL ||
list->next == NULL)
{
return list;
}
node *p1 = list;
node *p2 = list;
while(p1&&p2&&p2->next)
{
p1=p1->next;
p2=p2->next->next;
}
if(p2->next==NULL ||
p2==NULL)
return
p1;
}
题目2:判断一个单向链表是否存在循环。
思路:定义2个指针,一个指针跑一步,一个指针跑2步;如果2个指针相遇,就存在循环,否则不存在循环。
int find_loop(node *head)
{
node *p
= NULL;
node *q
= NULL;
if (head
== NULL)
return 0;
p =
head;
q =
head->next;
while
(p!=NULL &&
q!=NULL&&q->next!=NULL&&p!=q)
{
p = p->next;
q = q->next->next
}
if
(p==q)
return 1;
else
return 0;
}
题目3:从一个字符串中删除某一个特定的字符。
如:”hello world”,’o’-->”hell wrld”
char* del_char(char *str,char ch)
{
char *p1=str,*p2=str;
if(NULL==str)
{
return NULL;
}
while(*p2!='\0')
{
if(*p2!=ch)
{
*p1++=*p2++;
}
else
{
p2++;
}
}
*p1='\0';
return str;
}
思考题1:将一个字符串按照单词顺序逆置,如hello this world-->world this hello
思考题2:将一个IP地址字符串转化为32位整数。如255.255.255.255-->0xffffffff
例1:请编写一个高效率的函数来找出字符串中的第一个无重复字符。例如,"total"中的第一个无重复字符上一"o"
char find_first_single_char(const char *str)
{
int tmp[256]={0};
char *s= (char *)str;
while(*s!='\0')
{
tmp[*s]++;
s++;
}
s=(char*)str;
while(*s!='\0')
{
if(tmp[*s]==1)
{
return *s;
}
s++;
}
return '\0';
#define MAXMUM 65536
void find_repeated_number(int a[], in n)
{
int tmp[MAXMUM];
int i;
for (i = 0; i < MAXMUM; i++)
{
tmp[i] = 0;
}
for (i = 0; i < n; i ++)
{
tmp[a[i]]++;
}
for (i = 0; i < MAXMUM; i++)
{
if
(tmp[i]>1)
printf(“%d:%d”, i, tmp[i]);
}
}
思考题1:从一个字符串中删除某一组特定的字符。
如:”hello world”,’o’,'l'-->”he wrd”
思考题2:用筛法找出40000以内的素数。
Copyright 2011-2020 © MallocFree. All rights reserved.