第2讲 变量与数据_第1页
第2讲 变量与数据_第2页
第2讲 变量与数据_第3页
第2讲 变量与数据_第4页
第2讲 变量与数据_第5页
已阅读5页,还剩47页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

编程规范—第二讲

变量与类型软件工程系变量和类型是程序的基础,也是编程中容易忽视的地方。本节将学习变量和类型的相关概念和编程陷阱。实用经验5:计算机是怎么存储变量的?实用经验6:确保每个对象在使用前已被初始化。实用经验7:掌握局部变量和全局变量的区别实用经验8:掌握变量定义的位置与时机。实用经验9:合理使用引用实用经验13:typedef使用的陷阱实用经验16:提防隐式转换带来的麻烦实用经验17:深刻理解void和void*实用经验18:如何判定变量是否相等?5:计算机如何存储变量数据在内存中的放置包括两个部分:一是数据放置的位置即数据存储区域二是数据放置的格式。数据存储区域可分为:只读数据区,全局静态存储区,自由存储区,栈区,堆区。(P23)只读/静态存储区:存储常量和恒值;全局静态存储区:全局变量和静态变量自由存储区:CRT通过malloc\free函数管理的内存。与堆区同一种管理方式,统称为堆区。栈区:数据由编译器自动分配释放,主要存放函数的参数值、局部变量等。堆区:存放new分配的内存块,编译器不负责他们的释放。堆区分配数据虽具有节省空间,使用方便。堆区分配和释放内存会造成内存空间的不连续,形成大量的碎片,程序效率降低。分配的数据如果没有释放,很容易造成内存泄露。思考:什么情形应该用到全局变量和静态变量,两者有什么区别

什么时候应该用到new/delte?5:计算机如何存储变量数据放置的格式:整型值整数,Int\short\long类型都表示整型值,不同之处是存储变量所占的内存空间大小不同,计算机按位序列存储数据,8位一个字节,一个字节和一个称为地址的数关联起来,并且需要知道存在该地址的值的类型。字符型

char基本字符集;wchar_t扩展字符集布尔值算术类型。Bool类型表示true(非0)和false(0)。浮点型单精度(32:1+8+23双精度(64:1+11+52)两类三部分符号位、指数位、尾数部分。浮点计算,三部分必须是二进制形式。5计算机如何存储变量变量在不同语境下,其分配内存区域是不同的。静态变量和全局变量一般分布在全局/静态存储区,函数的参数和变量一般分配在栈中,new和malloc申请的数据一般分配在堆中。变量在定义和初始化时,一定要注意其取值范围,以防出现数据截断异常现象。同样数据在使用过程中应禁止降级强制转换,这种转换一般会降低数据的精度,严重时会出现模棱两可的问题。6:确保每个对象在使用前已被初始化对象在使用前是否会被初始化是无法确定的如:Pt的成员变量有时会被初始化为0,有时不会最佳的处理方式就是:永远在变量被使用之前将它初始化

内置数据类型,必须手动完成初始化。

如:Inti=5;char*pszString=“acstring”内置类型以外的其他成员,对象的构造函数完成初始化classCPoint{public: intm_iX;intm_iY;};intmain(){CPointpt;cout<<pt.m_iX<<endl;return0;}读取未初始化的对象会导致不确定的行为6确保每个对象在使用前已被初始化最佳的处理方式就是:永远在变量被使用之前将它初始化内置类型以外的其他成员,对象的构造函数完成初始化

typedefenumtagsex{MALE_SEX=0,FEMALE_SEX=1,}Sex;classCPerson{public:stringm_strName;Sexm_sex;CPerson(stringstrName,Sexsex);};CPerson::CPerson(stringstrName,Sexsex){m_strName=strName;m_sex=sex;}intmain(){CPersonc1("lucy",MALE_SEX);cout<<c1.m_strName<<c1.m_sex<<endl;return0;}typedefenumtagsex{ MALE_SEX=0,FEMALE_SEX=1,}Sex;classCPerson{public:stringm_strName;Sexm_sex;CPerson(stringstrName,Sexsex);};CPerson::CPerson(stringstrName,Sexsex):m_strName(strName),m_sex(sex){}intmain(){CPersonc1("lucy",MALE_SEX);cout<<c1.m_strName<<c1.m_sex<<endl;return0;}复制初始化:之前需调用默认构造函数,效率低列表初始化:无需调用默认构造函数,效率高,且常成员只能用列表初始化声明次序决定初始化新婚许列表次序应与声明次序一致6确保每个对象在使用前已被初始化Tips:为内置类型对象进行手动初始化,因为C++不保证初始化它们。构造函数最好使用成员初始化列表,而不是在构造函数本体内使用赋值操作。初值列表列出的成员变量,其排列顺序应和它们在class中的声明次序相同。

7局部变量和全局变量的差别变量一般包括4种:全局变量,静态全局变量,静态局部变量,和局部变量。按照存储区域分:全局变量,静态全局变量,静态局部变量都存放在内存的静态存储区,局部变量存放在内存的栈区。按照作用域分:全局变量在整个工程文件内都有效;静态全局变量只在定义它的文件内有效;静态局部变量只在定义它的函数内有效,只是程序分配一次内存,函数返回后,该变量不会消失;局部变量在定义它的函数内有效,函数返回后失效。注意:全局变量和静态变量如果没有手动初始化,则由编译器初始化为0.局部变量是编译器永远不会初始化的变量。如果没有手动初始化,则为随机值7局部变量和全局变量的差别局部变量也称为内部变量.局部变量是在函数内说明的,其作用域仅限于函数内,离开该函数后再使用变量是非法的。右图在函数f1内定义了3个变量,a为形参,b,c为一般变量,abc的作用于仅限于函数f1。局部变量说明主函数main定义的变量只在其中使用,不能用于其他函数。同时也不能使用其他函数的变量。C++语言应注意。形参变量属于被调函数的局部变量,实参属于主调函数的局部变量。在复合语句中可定义变量,其作用域只在复合语句范围内。Intf1(inta){Intb,c;……}Main(){Intm,n;F1(m)}局部变量定义及特点局部变量例子7局部变量和全局变量的差别本程序在main中定义了i,j,k3个变量,其中k未赋初值。而在复合语句内有定义了一个变量k,并赋初值为8.请问两个输出语句的值分别是?Main(){Inti=2;j=3;k;k=i+j;{intk=8;If(i==3){printf(“%d\n”,k);}}Printf(“%d\n%d\n”,i,k)}7局部变量和全局变量的差别

全局变量也称为外部变量,他是在函数外部定义的变量。他不属于某一个函数,属于一个源文件。全局变量例子程序要求:函数vs完成求长方形体积和3个面积,函数返回体积V。主函数完成长宽高的输入及体积及三个面积的输出。矛盾分析:C语言规定函数返回值只有一个,vs函数将体积返回给主函数,其余三个面积值怎么传递给主函数解决方法:定义三个面积为外部变量s1,s2,s3,其作用域为整个程序。主函函数也可以使用ints1,s2,s3;intvs(inta,intb,intc){intv;v=a*b*c;s1=a*b;s2=b*c;s3=a*c;returnv;}intmain(){intv,l,w,h;cout<<"inputlength,widthandheight\n";cin>>l>>w>>h;v=vs(l,w,h);cout<<v<<"\n"<<s1<<"\n"<<s2<<"\n"<<s3;}全局变量定义及特点函数的return语句只能返回一个值体积v需要输出vs函数计算所得的四个值?声明为全局变量,无需return语句传回7局部变量和全局变量的差别全局变量说明:对于外部变量的说明和定义不是一回事。

外部变量定义:必须在所有函数之外,且只定义一次:[extern]inta,b;(可省略extern.)

外部变量说明:出现在要使用该外部变量的各个函数中,

在整个程序内可能出现多次:externinta,b;外部变量在定义时就分配了内存单元,外部变量定义可做初始赋值。外部变量说明不再赋初始值,只是说明。外部变量加强了函数模块之间的联系,同时使得函数依赖这些变量,因此导致函数独立性低。基于模块化程序设计,在不必要的情况下尽可能少使用外部变量。同一源文件,允许全局变量和局部变量同名7局部变量和全局变量的差别变量特性

生存期静态存储变量变量定义就分配存储单元直至整个程序结束。

静态存储变量有:extern全局变量静态变量static

动态存储变量程序执行过程才分配存储单元,使用完毕立即释放动态存储变量有:auto自动变量register寄存器变量静态存储变量是一直存在的,而动态存储变量则时而存在时而消失。作用域全局作用域、局部作用域、语句作用域、类作用域、命名空间作用域、文件作用域。全局变量具有全局作用域,可以作用于所有源文件。不包含全局变量定义的源文件只需要用extern关键字再次声明。静态局部变量具有局部作用域,他只被初始化一次,直到程序运行结束都一直存在。只对定义自己的函数体始终可见。局部变量只有局部作用域,自动对象,在程序运行期间不是一直存在的,函数执行期间存在,函数调用结束变量撤销内存收回。静态全局变量具有全局作用域,他只作用于他的文件中,不作用于其他文件(区别于全局变量)。即被static关键字修饰过的变量具有文件作用域。7局部变量和全局变量的差别Tips:A.若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度;B.若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;C.设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题,因为他们都放在静态数据存储区,全局可见;D.如果我们需要一个可重入的函数,那么,我们一定要避免函数中使用static变量(这样的函数被称为:带“内部存储器”功能的的函数)E.函数中必须要使用static变量情况:比如当某函数的返回值为指针类型时,则必须是static的局部变量的地址作为返回值,若为auto类型,则返回为错指针。8:掌握变量定义的位置与时机tips:在定义变量时,要三思而后行,掌握变量定义的时机与位置,在合适的时机与合适的位置上定义变量。尽可能推迟变量的定义,直到不得不需要该变量为止;同时,为了减少变量名污染,提高程序可读性,尽量缩小变量的作用域。总结:变量定义离使用越远越好;尽量缩小变量作用域优点:

避免构造和析构非必要对象,甚至避免默认构造函数执行

避免命名污染8掌握变量定义的位置与时机std::stringChangToUpper(conststd::string&str){usingnamespacestd;stringupperStr;if(str.length()<=0){throwerror("Stringtobechangedisnull");}...//将字符变为大写

returnupperStr;

}结论:就算函数抛出了异常,因变量定义在先。仍然要为upperStr的构造与析构付出代价。所以变得精明些,把握变量定义的时机:尽量晚地去定义变量,直到不得不定义时。过早定义upperstr对象如果输入字符串为空,函数抛出异常,upperstr对象就不会被使用。掌握变量定义的位置与时机std::stringChangToUpper(conststd::string&str){usingnamespacestd;if(str.length()<=0){throwerror("Stringtobechangedisnull");}stringupperStr;...//将字符变为大写

returnupperStr;}关于变量定义的位置,建议变量定义得越“local”越好,尽量避免变量作用域的膨胀。这样做不仅可以有效地减少变量名污染,还有利于代码阅读者尽快找到变量定义,获悉变量类型与初始值,使阅读代码更容易。8掌握变量定义的位置与时机#include<iostream>#include<string>usingnamespacestd;stringgetSubStr(conststring&str,size_tiPos){stringstrSubStr;if(str.size()<iPos){throwlogic_error("iPosistoolarge");}strSubStr=str.substr(iPos);returnstrSubStr;}intmain(){strings1("hello");cout<<getSubStr(s1,7);return0;}#include<iostream>#include<string>usingnamespacestd;stringgetSubStr(conststring&str,size_tiPos){if(str.size()<iPos){throwlogic_error("iPosistoolarge");}stringstrSubStr;strSubStr=str.substr(iPos);returnstrSubStr;}intmain(){strings1("hello");cout<<getSubStr(s1,7);return0;}该语句根本就不会执行,字符串对象trSubStr也没有声明的必要Hello总共6位,获取从第8位开始的子字符串,一定出现异常推迟对象声明,但是对象的默认构造函数仍会执行#include<iostream>#include<string>usingnamespacestd;stringgetSubStr(conststring&str,size_tiPos){if(str.size()<iPos){throwlogic_error("iPosistoolarge");}stringstrSubStr(str.substr(iPos));returnstrSubStr;}intmain(){strings1("hello");cout<<getSubStr(s1,7);return0;}8掌握变量定义的位置与时机for(inti=0;i<N;i++){...//dosomething}...//somecodefor(inti=0;i<M;i++){...//doanotherthing}原因分析:这是因为在VC6.0中,i的作用域超出了本身的循环。存在变量名污染变量名污染著名例子明明两个i都属于各自for语句内的局部变量作用域已经足够小上述代码在VC6.0中是不能通过编译的,编译器会提示变量i重复定义。说明:1.微软意识到了这个问题,在其后续的VC++系列产品中,i的作用域重新被限定在了for循环体2.变量名污染的本质就是同一作用域下的命名冲突9:引用难道只是别人的替身引用的定义及特点引用只是默认值的别名,对引用的唯一操作就是将其初始化。一旦引用初始化结束,引用就只是其默认值的另一种写法。引用没有地址,甚至不占用任何存储空间。引用只可作为

变量的别名复杂的左值(有内存地址)表达式的别名引用的用途如果一个函数返回一个引用,这说明此函数的返回值可重新赋值引用的另一个用途即让函数在其返回值之外传递几个值。另外,指向数组的引用保留了数组的长度信息,而指针不会保留数组的长度信息。常量值不能给普通引用初始化,但是可以给const引用初始化:

constint&rInt=12;//正常通过;

int&rInt=12//错误9:引用难道只是别人的替身引用的用途int&put(intn);intvals[10];interror=-1;voidmain(){put(0)=10;put(9)=20;cout<<vals[0];cout<<vals[9];}int&put(intn){if(n>=0&&n<=9)returnvals[n];else{cout<<"subscripterror";returnerror;}}#include<iostream>#include<string>usingnamespacestd;intvs(inta,intb,intc,int&s1,int&s2,int&s3){intv;v=a*b*c;

s1=a*b;

s2=b*c;

s3=a*c;returnv;}intmain(){intl,w,h,v,s1,s2,s3;cout<<"inputlength,widthandheight\n";cin>>l>>w>>h;v=vs(l,w,h,s1,s2,s3);cout<<v<<"\n"<<s1<<"\n"<<s2<<"\n"<<s3;}只能返回体积,其余的三个面的面积怎么传递回主函数说明:如果一个函数返回一个引用,这说明此函数的返回值可重新赋值说明:引用的另一个用途即让函数在其返回值之外传递几个值。9:引用难道只是别人的替身引用的用途#include<iostream>#include<string>usingnamespacestd;voidArray_test1(int(&array)[3]){array[2]=3;}voidArray_test2(intarray[3]){array[2]=3;}intmain(){intn3[2]={2,4};Array_test1(n3);Array_test2(n3);}编译不能通过编译能通过指向数组的引用,保留数组的长度为3,却传来一个长度为3的数组指向数组的指针,只记住数组的首地址说明:指向数组的引用,可以保留数组长度9:引用难道只是别人的替身引用的用途#include<iostream>#include<iomanip>usingnamespacestd;intmain(){inta;int&b=a;constint&c=12.3;cout<<c;shorts=123;constint&rIntegrate=s;s=321;constint*ip=&rIntegrate;cout<<"rIntegrate="<<rIntegrate<<",s="<<s<<endl;cout<<"ip="<<ip<<",&s="<<&s;return0;引用初始化值为左值没问题引用默认值不是左值,必须前面加constrInte与s值并不相同,说明rInte默认值并不是s,而是一个临时对象说明:常量引用与临时变量共存亡9引用难道只是别人的替身小心陷阱:如果初始化值是一个左值(可以取得地址),则可以初始化引用。如果初始化值不是一个左值,则只能对constT&(常量引用)赋值,且赋值过程包括3阶段:首先将值隐式转换到类型T,然后将这个转换结果存放在一个临时对象中,最后用这个临时对象来初始化这个引用变量。constT&(常量引用)过程中使用的临时变量会和constT&共“存亡”。constint&rInt=12;//对变量引用的任何操作都会影响匿名临时变量,而不会影响常量12.并且编译器会保证临时对象生命期扩展到初始化后的引用存在的全部时域。Tips:若非必要请不要使用const引用,因为引用有时会伴随着临时对象的产生。在函数生命中,请尽量避免const引用形参声明,使用非const引用形参替代以防因返回const引用生成的临时变量而导致程序执行错误。a=b+100;a就是左值,b+25就是一个右值。两边不可互换。13:typedef使用的陷阱Typydef与宏的混用陷阱#include<iostream>#definePSTR_MACROchar*typedefchar*PSTR;usingnamespacestd;intmain(intargc,char*argv[]){PSTRpiVar1,piVar2;PSTR_MACROpiVar3,piVar4;chariVar=100;piVar1=&iVar;piVar2=&iVar;piVar3=&iVar;piVar4=iVar;cout<<(void*)piVar1<<endl;cout<<(void*)piVar2<<endl;cout<<(void*)piVar3<<endl;cout<<piVar4<<endl;return0;}Typedef是给数据类型定义一个别名宏定义只是简单的字符串替换13:typedef使用的陷阱typedef

string

NAME;

typedef

int

AGE;#define

MAC_NAME

string

#define

MAC_AGE

int

typedef

int*

PTR_INT1;

#define

int*

PTR_INT2

int

main()

{

PTR_INT1

pNum1,

pNum2;

PTR_INT2

pNum3,

pNum4;

int

year

=

2011;

pNum1

=

&year;

pNum2

=

&year;

pNum3

=

&year;

pNum4

=

&year;

cout<<pNum1<<"

"<<pNum2<<"

"<<pNum3<<"

"<<pNum4;

return

0;

}

Typydef与宏的混用陷阱stringa,b;intc,d;

stringa,b;intc,d;

MAC_NAMEa,b;MAC_AGEc,d;

stringa,b;intc,d;

PTR_INT1

pNum1,

pNum2;

PTR_INT2

pNum3,

pNum4;

int*pNum1,

*pNum2;int*pNum3,

pNum4;13:typedef使用的陷阱Typedef其他多种用途:(1)简化代码在部分较老的C代码中,声明struct对象时,必须带上struct关键字,即采用“struct结构体类型结构体对象”的声明格式。

为了在结构体使用过程中,少写声明头部的struct,于是就有人使用了typedef:struct

tagRect

{

int

width;

int

length;

};

strcut

tagRect

rect;

typedef

struct

tagRect

{

int

width;

int

length;

}RECT;

RECT

rect;

说明:在现在的C++代码中,这种方式已经不常见,因为对于结构体对象的声明已经不需要使用struct了,可以采用“结构体类型结构体对象”的形式。13:typedef使用的陷阱(2)用typedef定义一些与平台无关的类型。例如在标准库中广泛使用的size_t的定义:(3)为复杂的声明定义一个简单的别名。它可以增强程序的可读性和标识符的灵活性,这也是它最突出的作用。说明:在typedef的使用过程中,还必须记住:typedef在语法上是一个存储类的关键字,类似于auto、extern、mutable、static、register等,虽然它并不会真正影响对象的存储特性,#ifndef

_SIZE_T_DEFINED

#ifdef

_WIN64

typedef

unsigned

__int64

size_t;

#else

typedef

_W64

unsigned

int

size_t

#endif

#define

_SIZE_T_DEFINED

#endif

typedef

static

int

INT2;

//不可行,编译将失败

声明:int*(*a[5])(int,char*);变量名为a,直接用一个新别名pFun替换a就可以了:

typedefint*(*pFun)(int,char*);

原声明的最简化版:pFuna[5];13:typedef使用的陷阱用途一:定义一种类型的别名,而不只是简单的宏替换。可以用作同时声明指针型的多个对象。用途二:用在旧的C的代码中(具体多旧没有查),帮助struct。以前的代码中,声明struct新对象时,必须要带上struct,即形式为:struct结构名对象名。用途三:用typedef来定义与平台无关的类型。用途四:为复杂的声明定义一个新的简单的别名。Tips:区分typedef与#define之间的不同;不要用理解宏的思维方式对待typedef,typedef声明的新名称具有一定的封装性,更易定义变量,而#define宏只是简单的自读替换。尽量用typedef实现那些复杂的声明形式,以保证代码清晰,易于阅读。16:提防隐式转换带来的麻烦在C/C++中,类型转换发生在这种情况下:为了实现不同类型的数据之间进行某一操作或混合运算,编译器必须把数据转换成为相同的数据类型。C/C++语言中的类型转换可以分为两种,一种为隐式转换,特指编译器完成的类型转换;而另一种则为显式强制转型特指由开发人员显式进行的数据类型转换。显式强制转型在某种程度上还有一定的优点,对于编写代码的人来说使用它能够很容易地获得所需类型的数据,对于阅读代码的人来说可以从代码中获知作者的意图。而隐式转换则不然,它让发生的一切变得悄无声息,在编译时这一切由编译程序按照一定规则自动完成,不需任何的人为干预。16:提防隐式转换带来的麻烦隐式转换虽然带来了一定的便利,使编码更加简洁,减少了冗余,但是这些并不足以让我们完全接受它,因为隐式转换所带来的副作用不可小觑,它通常会使我们在调试程序时毫无头绪。上述代码片段中的函数调用不会出现任何错误,编译器给出的仅仅是一个警告。可是细心的程序员一眼就能看出问题:函数Function(charc)的参数c是一个char型,256绝不会出现在其取值区间内。但是编译器会自动地完成数据截断处理。编译器悄悄完成的这种转换存在着很大的不确定性:一方面它可能是合理的,因为尽管类型long大于char,但para中很可能存放着char类型范围内的数值;另一方面para的值的确可能是char无法容纳的数据,这样一不小心便会造成一个非常隐蔽、难以捉摸的错误。void

Function(char

c);

int

main()

{

long

para

=

256;

Function(para);

return

0;

}

16:提防隐式转换带来的麻烦C/C++隐式转换主要发生在以下几种情形。1、内置类型间的隐式转换在混合类型的表达式中,操作数转换成相同的类型;用作if语句或循环语句的条件时,被转换为bool类型;用于switch语句时,被转换为整数类型;用来初始化某个变量(函数实参、return语句)时,被转换为变量的类型。内置类型的转换级别:Double>float>longlong/unsignedlonglong>long/unsignedlong>int/unsignedint>short/unsignedshort>char/unsignedchar16:提防隐式转换带来的麻烦在编译这段代码时,编译器会按照规则自动地将ival转换为与dval相同的double类型。C语言规定的转换规则是由低级向高级转换。两个通用的转换原则是:(1)为防止精度损失,类型总是被提升为较宽的类型。(2)所有含有小于整型类型的算术表达式在计算之前其类型都会被转换成整型。它最直接的害处就是有可能导致重载函数产生二义性。(右图)参数0.5应该转换为ival还是fval?这是编译器没法搞明白的一个问题。int

ival

=

3;

double

dval

=

3.1415

cout<<(ival

+

dval)<<endl;

//ival被提升为double类型:3.0

extern

double

sqrt(double);

sqrt(2);

//2被提升为double类型:

2.0void

Print(int

ival);

void

Print(float

fval);

int

ival

=

2;

float

fval

=

2.0f;

Print(ival);

//

OK,

int-version

Print(fval);

//

OK,

float-version

Print(1);

//

OK,

int-version

Print(0.5);

//

ERROR!!

16:提防隐式转换带来的麻烦non-explicitconstructor接受一个参数的用户定义类对象之间隐式转换。在上面的代码(右图)中,调用DoSomething()函数时会发现实参与形参类型不一致,但是因为类A的构造函数只含有一个int类型的参数,所以编译器会以20为参数调用A的构造函数,以便构造临时对象,然后传给DoSomething()函数。不要为此而感到惊讶,其实编译器比想像的还要聪明:当无法完成直接隐式转换的时候,它不会罢休,它会尝试使用间接的方式。所以,下面的代码也是可以被编译器接受的:classA{public:A(intx):m_data(x){}private:intm_data;}voidDoSomething(AaObject);DoSomething(20);voidDoSomething(AaObject);floatfval=20.0;DoSomething(fval);16:提防隐式转换带来的麻烦控制隐式转换的两条有效途径之一:使用具名转换函数为了避免该问题出现,建议使用自定义转换具名函数代替转换操作符ClassString{pubic:operatorconstchar*();//在需要时,string对象可以转换成constchar*指针};//假设s1s2均是string类型的字符串Intx=s1-s2;//可编译,单行为不确定Constchar*p=s1-5;//可编译,但行为不确定P=s1+‘0’//可编译,单不是开发人员期望的结果If(s1==“0”){……}’//可编译,单不是开发人员期望的结果ClassString{pubic:constchar*as_char_pointer()const;//string对象转换成constchar*指针};//假设s1s2均是string类型的字符串Intx=s1-s2;//编译错误Constchar*p=s1-5;//编译错误P=s1+‘0’//编译错误If(s1==“0”){……}’//编译错误16:提防隐式转换带来的麻烦控制隐式转换的两条有效途径之二:使用explicit限制的构造函数这种方式针对的是具有一个单参数构造函数的用户自定义类型。上述代码片段(右图)中,用户自定义类型Widget的构造函数可以是一个参数,也可以是两个参数。具有一个参数时,其参数类型可以是unsignedint,亦可以是char*。所以这两种类型的数据均可以隐式地转换为Widget类型。控制这种隐式转换的方法很简单:为构造函数加上explicit关键字:class

Widget

{

public:

Widget(

unsigned

int

factor);

Widget(

const

char*

name,

const

Widget*

other

=

NULL);

};

Widgetwidget1=100;//可通过编译Widgetwidget1=“mywindow”//可通过编译class

Widget

{

explicit

Widget(unsigned

int

factor);

explicit

Widget(const

char*

name,

const

Widget*

other

=

NULL);

};

Widgetwidget1=100;//编译错误Widgetwidget1=“mywindow”//编译错误16:提防隐式转换带来的麻烦提防隐式转换所带来的微妙问题,尽量控制隐式转换的发生;通常采用的方式包括:(1)使用非C/C++关键字的具名函数,用operatoras_T()替换operatoT()(T为C++数据类型)。(2)为单参数的构造函数加上explicit关键字。在隐式转换中,转型并非仅仅将一种类型转换为另外一种,参看p71实例1、实例2。Tips:在使用编译器隐式类型转换时,一定要注意,能减少隐式转换使用时尽量减少隐式转换的使用;除非明确知道隐式转换时编译器发生了什么,否则在编译时不要对编译器隐式转换进行假设。17:正确区分void与void*void是“无类型”,所以它不是一种数据类型;void*则为“无类型指针”,即它是指向无类型数据的指针,也就是说它可以指向任何类型的数据。从来没有人会定义一个void变量,如果真的这么做了,编译器会在编译阶段清晰地提示,“illegaluseoftype‘void’”。void体现的是“有与无”的问题,要先“有”了,在非void的前提下才能去讨论这个变量是什么类型的。void发挥的真正作用是限制程序的参数与函数返回值。voida;//定义无类型变量,无意义

void*p;//定义无类型指针,会有意义intb;p=&b;inta;(void)a;//强制转换变量类型char*p;(void*)p//强制转换指针类型17:正确区分void与void*在C/C++语言中,对void关键字的使用做了如下规定:(1)如果函数没有返回值,那么应将其声明为void类型。在C语言中,凡不加返回值类型限定的函数,就会被编译器作为返回整型值处理。程序运行的结果为:2+3=5。这个结果更加明确地说明了函数返回值为int类型,而非void。为了避免出现混乱,在编写C/C++程序时,必须对任何函数都指定其返回值类型。如果函数没有返回值,则要声明为void。这既保证了程序良好的可读性,也满足了编程规范性的要求。Add

(

int

a,

int

b

);

int

main()

{

printf

(

“2

+

3

=

%d",

Add

(

2,

3)

);

return

0;

}

Add

(

int

a,

int

b

)

{

return

a

+

b;

}

17:正确区分void与void*(2)如果函数无参数,那么声明函数参数为void。如右图代码段,在C++编译器中编译代码时则会出错,提示“'TestFunction':functiondoesnottake1parameters”。在C/C++中,若函数不接受任何参数,一定要指明参数为void。int

TestFunction(void)int

TestFunction()//

{

return

1;

}

int

main()

{

int

thisYear

=

TestFunction(2);

//

processing

code

return

0;

}

17:正确区分void与void*(3)特殊指针类型void*。按照ANSI标准,对void指针进行算术操作是不合法的:ANSI标准之所以这样认定,是因为只有在确定了指针指向数据类型的大小之后,才能进行算术操作。但是大名鼎鼎的GNU则有不同的规定,它指定void*的算法操作与char*一致。所以在上面代码片段中出现错误的代码在GNU编译器中能顺利通过编译,并且能正确执行。虽然GNU较ANSI更开放,提供了对更多语法的支持,但是ANSI标准更加通用,更加“标准”,所以在实际设计中,还是应该尽可能地迎合ANSI标准。void

*

pVoid;

pVoid

++;

//

VC++错误,error

C2036:

“pVoid*”:

未知的大小(GNU,正确)

pVoid

+=

1;

//

VC++错误

(GNU,正确)

int

*

pInt;

pInt

++;

//

正确,pInt指针增大sizeof(int)

pInt

+=

2;

//正确,

pInt指针增大2*sizeof(int)17:正确区分void与void*(3)特殊指针类型void*。在实际的程序设计中,为迎合ANSI标准,并提高程序的可移植性,可以采用以下方式进行代码设计:(4)如果函数的参数可以是任意类型指针,那么应声明其参数为void*.最典型的例子就是我们熟知的内存操作函数memcpy和memset的原型:任何类型的指针都可以传入memcpy和memset中,传出的则是一块没有具体数据类型规定的内存,这也真实地体现了内存操作函数的意义。如果类型不是void*,而是char*,那么这样的memcpy和memset函数就会与数据类型产生明显联系,纠缠不清。void

*

pVoid;

(char

*)pVoid

++;

//

ANSI:正确;GNU:正确

(char

*)pVoid

+=

2;

//

ANSI:错误;GNU:正确

void

*

memcpy(void

*dest,

const

void

*src,

size_t

len);

void

*

memset

(

void

*

buffer,

int

c,

size_t

num

);

17:正确区分void与void*(5)void不能代表一个真实的变量。Void体现了一种抽象,他的出现只是因为一种抽象的需要,如果你正确的理解了面向对象的抽象基类的概念,也就很容易理解void数据类型。正如不能给一个抽象基类定义实例一样,我们不能定义void变量。void与void*是一对极易混淆的双胞胎兄弟,但是它们在骨子里却存在着质的不同,区分它们,按照一定的规则使用它们,可以提高程序的可读性、可移植性。void

a;

Function(voida)

//

错误

18:如何判定变量是否相等判断两个变量是否相等,有两点非常重要:一是两个变量分别都是什么类型。首先两个变量的类型应该是一致的,如果不一致,那么判定其是否相等则无疑义。二是变量相等的判断依据是什么?判断两个变量是否相等时,两个变量类型必须相同。每种类型的变量,其判定依据各不相同,有些可判断,有些无法判断是否相等。只有那些允许判定是否相等的变量才可以判断是否相等。变量类型:布尔变量、整型变量、浮点型变量、字符型变量、指针变量。18:如何判定变量是否相等Bool变量一般描述某一操作执行成功与否,成功返回true,否则返回false。布尔类型的变量一般无法判定是否相等,判断两个布尔变量是否相等没有意义。整形值包括(unsigned)char,short,int这几种数据类型。判定两个整型变量是否相等,一般包括以下两个步骤:1、应保证两个整型值为同一类型,如果不同,C++编译器会默认将类型阶低的变量转换为阶高的变量。即存在潜在的隐式转换,会带来意想不到的麻烦。(p78)而显示转换更不可取。2、如果类型同,通过“==”操作符即可进行两个整型变量是否相等的判定。18:如何判定变量是否相等浮点型变量包括单精度浮点型和双精度浮点型。浮点型的比较不能通过简单的“==”进行判定,必须通过差值的绝对值的精度判定。这是由浮点数据在内存中存放的格式决定的。一个字符一般由多个字符组成。两个字符串相等要满足两个条件:一是字符串的长度必须相等;而是两个字符串的每个字符必须相等。18:如何判定变量是否相等C语言标准库提供几个标准函数,可比较两个字符串是否相等,它们是strmp和strmpi.。strcmp对两个字符串进行大小写敏感的比较,strcmpi对两个字符串进行大小写不敏感的比较。指针变量是C++中功能最强大,也是出现问题最多的地方,比较时,必须保证是同一类型的指针变量。且无法进行大于小于的比较。18:如何判定变量是否相等#include<iostream>usingnamespacestd;intmain(){char

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论