<1>C++和C的主要差异——符号常量和标准输入输出流
符号常量,也称const常量,是用来表示一个常 量的标识符。定义const常量的语法 格式为: const <类型> <常量名>=<表达式> ; 例如: const double PI=3.1415926; 提示: (1)在程序中使用符号常量可以提高程序的可读性和可维护性。例如将数值计算中经常使用的一些参数定义为符号常量,当需要改变参数数值时,只需要更改符号常量的定义语句就行了。 (2)在编程时,符号常量同变量一样,都必须"先定义,后使用”。
在C语言中,输入输出通过调用scanf()和printf()来实现,而C+ +中则是使用类对象cin和cout来实现。 cin是系统在命名空间std中预先定义好的标准输入流对象,代表标准输入设备——键盘。 当程序需要从键盘输入时,可以使用提取运算符">>" 从输入流对象cin中提取从键盘输入的字符或数字,并将其存储到指定变量所在的内存空间。 cout是系统预先定义好的标准输出流对象,代表标准输出设备——屏幕。 当程序需要向屏幕显示输出时,可以使用插入运算符“< <"将字符或数字插入到输出流对象cout上,就可以将其显示在屏幕上。
假设已经定义了两个变量: int a,b; 简单的输入语句如: cin>>a>>b; 简单的输出语句如: cout<< "输入的两个整数分别是"<<a<<"和"<<b; 为了更好地控制输入/输出格式,C++提供了格式控制函数和格式控制符。 控制符是在头文件iomanip中定义的对象,可以将控制符直接插入流中。如 dec、hex、 oct、 setfill(c)等。 【例1-4】将用户输入的一个整数分别以十进制、八进制和十六进制的格式输出。 #include<iostream> #include<iomanip> using namespace std; int main() { int num; cout<<"请输入一个整数:"; cin>>num; cout<<"十进制形式:"<<num<<endl; cout<<"八进制形式:"<<oct<<num<<endl; cout <<"十六进制形式:"<<hex<<num<<endl; cout<<dec; // 恢复默认设置 return 0; }<2> C++和C的主要差异——内联函数
》》运行环境的保存和恢复、函数跳转都要消耗一定的时间。 如果被调用函数实现的功能比较复杂,其计算时间会远远大于函数调用所额外消耗的时间,此时函数调用所带来的额外时间开销就可以忽略不计。但是如果被调用函数实现的功能非常简单并且将被非常频繁调用,在编写程序时就必须要考虑因函 数调用所造成的额外时间开销。 》》为了解决上述问题,一种比较好的方案就是使用内联函数。在编译程序时,系统会直接将调用内联函数的地方用内联函数中的语句体做等价替换 。这样,在程序运行时就不需要函数调用,从而避免运行环境保存和恢复以及函数跳转所引起起的额外时间开销,提高程序的执行效率。
内联函数定义的一般格式如下: inline <函数类型> <函数名>([<形参表>]) { 函数体 } 在函数定义的<函数类型>前加上inline关键字,即为内联函数的定义。例如, 对用于求圆面积的函数的功能非常简单,为了避免函数调用所引起的额外时间 开销,可以将它定义成内联函数: inline double CircleArea(double r) { return 3.14*r*r; }提示: ( 1 )内联函数只适用于功能简单的小函数。对于函数体中包含循环、switch等复杂结构控制语句的函数及语句比较多的函数,即便该函数被定义为内联函数,编译器也往往会放弃内联方式,将其作为普通函数处理。 ( 2 )必须在内联函数定义处给出inline关键字。如果仅在函数声明时给出inline关键字,而在函数定义时未给出inline关键字,则该函数会被编译器视为普通数。
<3>C++与C的主要差异—— 带默认形参值的函数
•在调用函数时,需要针对函数中的每一个形参给出对应的实参。C++中也允许在函数定义或函数声明时给出默认的形参值。在调用函数时,对于有默认值的形参,如果没有给出相应的实参,则函数会自动使用默认形参值;如果给出相应的实参,则函数会优先使用传入的实参值。 • 指定默认形参值的位置:默认形参值可以在两个位置指定:如果有函数声明,则应在函数声明处指定;否则,直接在函数定义中指定。
【例1-5】默认形参值使用方法示例。 #include <iostream> using namespace std; void f(char *str="abc"); //默认值在函数声明处指定 int main() { f(); // 没有传入实参,此时形参str使用默认值"abc",执行后输出“abc” f("def");// 传入实参,此时形参str的值为"def",执行后输出“def” return 0; } void f(char *str) //此处不再给出默认值 { cout<<str<<endl; }• 提示: • 对于有默认值的形参,如果在调用函数时给出了相应的实参,则会优先使用传入 的实参值。如执行“f(“def”);”会输出def。 • 如果有函数声明,则应在函数声明中给出默认形参值,函数定义中不要重复。比如: • void f(char *str = “abc”); // 函数声明部分 • void f(char *str = “abc”) // 函数定义部分。错误:默认形参值被重复指定 • { … } • 默认形参值可以是全局常量、全局变量,甚至是可以通过函数调用给出,但不能是局部变量。因为形参默认值或其获取方式需在编译时确定,而局部变量在内存中的位置在编译时无法确定。
• 默认形参值的指定顺序 • 默认形参值必须严格按照从右至左的顺序进行指定。 比如: • void f(int a=1, int b, int c=3, int d=4); • 这种指定默认形参值的写法有误。这是由于在第2个参数b未指定默认形参值的情况下,给出了第1个参数a的默认形参值,不符合从右至左的指定顺序。
【例1-6】默认形参值的指定顺序示例。 #include <iostream> using namespace std; void f(int a, int b=2, int c=3, int d=4) // 带默认形参值的函数定义 { cout<<a<<" "<<b<<" "<<c<<" "<<d<<endl; } int main() { f(1); // 输出1 2 3 4 f(5, 10); // 输出5 10 3 4 f(11, 12, 13); // 输出11 12 13 4 f(20, 30, 40, 50); // 输出20 30 40 50 // f(); // 错误 return 0; }<4>C++与C的主要差异 ——函数重载
C++允许不同的函数具有相同的函数名,这就是函数重载。 当调用一个函数时,除了要写出函数名,还要根据函数的形参列表传递实参值。 对于函数名相同的多个函数,要在调用时能够区分开到底要调用哪个函数,只能根据传递实参在数量或类型上的不同来进行判断。也就是说,函数名相同的函数形参列表不能完全一样,否则会因无法区分而报错。
【例1-7】绝对值函数的重载。 提示:三个myabs函数形参的数据类型不同,因此,在调用myabs函数时, 系统会根据传入的实参类型决定调用哪个myabs函数。 #include <iostream> using namespace std; int myabs(int x); float myabs(float x); double myabs(double x); int main() { int a = -5; float b = -3.2f; double c = -4.75; cout<<myabs(a)<<endl; cout<<myabs(b)<<endl; cout<<myabs(c)<<endl; return 0; } int myabs(int x) { out<<"int abs(int x)被调用!"<<endl; return (x<0)?-x:x; } float myabs(float x) { cout<<"float abs(float x)被调用!"<<endl; return (x<0)?-x:x; } double myabs(double x) { cout<<"double abs(double x)被调用!"<<endl; return (x<0)?-x:x; } 【例1-8】最大值函数的重载。 提示:两个max函数形参的数据类型虽然相同,但数量不同,因此,在调用 max函数时,系统会根据传入的实参数量决定调用哪个max函数。 // max.cpp #include <iostream> using namespace std; int max(int x, int y); int max(int x, int y, int z); int main() { int a = 5, b = 10, c = 15; cout<<max(a, b)<<endl; cout<<max(a, b, c)<<endl; return 0; } int max(int x, int y) { cout<<"int max(int x, int y)被调用!"<<endl; return (x>y)?x:y; } int max(int x, int y, int z) { int c; cout<<"int max(int x, int y, int z)被调用!"<<endl; c = (x>y)?x:y; return (c>z)?c:z; }提示: 功能相近的函数才有必要重载,互不相关的函数进行重载会降低程序的可读性。 重载的函数必须在形参列表上有所区别。如果仅仅是返回类型不同,不能作为重载函数。比如: int myabs(int a); float myabs(int b); // 错误:与“int myabs(int a);”相比只有返回类型不同,不构成重载 避免默认形参所引起的函数二义性。比如: int max(int a, int b); int max(int a, int b, int c=0); 从形式上来看,两个max函数的形参数量不同,符合函数重载的条件。但实际上,两个max函数都可以通过“max(a, b)”的形式进行调用,此时就产生了二义性。
<5>C++与C的主要差异——动态内存分配和释放
在C++中,除了可以通过定义变量的方式来使用内存空间外,还可以使用new和delete两个关键字进行内存空间的动态分配和释放。使用动态方式分配内存 时会在堆区分配内存空间来存储数据,因此动态内存分配也通常称为堆内存分配。相应地,动态内存释放也通常称为堆内存释放。 堆内存分配new的语法格式为: new <数据类型>; <数据类型>指定了分配的内存空间中存储的数据的类型,由于不同类型的数据需要不同尺寸的内存空间来保存,因此,<数据类型>不同,分配的内存空间大小也会不同。
堆内存分配成功后,会返回动态分配的内存空间的首地址。通常将该首地址保存在一个指针变量中,以便后面可以使用该指针变量操作内存空间中的数据。 在分配内存的同时,还可以进行内存初始化工作: new <数据类型>(<表达式>); 其中,<表达式>确定了分配的内存空间中初始存储的数据。 例如: int *p; p=new int(3); 两条语句执行结束后,指针变量p就指向了通过new int动态分配的内存空间, 如图所示 也可以动态分配用于存储多个数据元素的内存空间,语法格式为: new <数据类型>[<表达式>]; 其中,<表达式>既可以是常量也可以是变量,但必须是整数,用于指定元素数目。例如: int *pArray; pArray=new int[3]; 两条语句执行结束后,指针变量pArray就指向了通过new int[3]动态分配的内存空间,如图所示。
实际上,可以将动态分配的内存空间看作是一个动态数组,使用指针访问堆内存的方式与使用指针访问数组的方式完全相同。唯一区别在于: 数组定义时,必须用常量表达式指定数组长度;而进行堆内存分配时,既可以使用常量表达式,也可以用变量表达式来指定元素数目。
使用new分配的内存必须使用delete释放,否则会造成内存泄露(即内存空间一直处于被占用状态,导致其他程序无法使用)。当系统出现大量内存泄露时,系统可用资源也会相应减少,从而导致计算机处理速度变慢,当系统资源枯竭时甚至会造成系统崩溃。 堆内存释放delete的语法格式为: delete []<指针表达式>; 其中,<指针表达式>指向待释放的堆内存空间的首地址。
delete []p; delete []pArray; 两条语句可以将前面使用new int(3)和new int[3]动态分配的内存空间释放。 如果<指针表达式>所指向的堆内存空间只包含一个元素,那么还可以将[]省掉, 即: delete <指针表达式>; 例如: delete p;
上面的语句可以将前面使用new int(3)动态分配的内存空间释放。由于pArray所指向的内存空间中包含3个元素,因此不能使用delete pArray来释放这些内存空间,而必须使用delete []pArray。 提示: 在使用new分配堆内存时要区分()和[]。()中的表达式指定了内存的初 值,而[]中的表达式指定了元素数目。例如: p=new int(3); //分配了1个int型元素大小的内存空间,且其初值为3 pArray=new int[3];//分配了3个int型元素大小的内存空间
【例1-9】使用堆内存分配方式实现学生成绩录入功能, 要求程序运行时由用户输入学生人数。 #include <iostream> using namespace std; int main() { int *pScore; int n, i; cout<<"请输入学生人数:"; cin>>n; pScore=new int[n];//按学生人数动态分配内存 if (pScore==NULL)//判断堆内存分配是否成功 { cout<<"堆内存分配失败!"<<endl; return 0; } //通过for循环输入学生成绩 for (i=0; i < n; i++) { cout<<"请输入第"<<(i+1) <<"名学生的成绩:"; cin>>pScore[i]; } //通过for循环输出学生成绩 for (i=0; i < n; i++) cout<<"第"<<(i+1) <<"名学生的成绩为:“ <<*(pScore+i)<<endl; //不再使用时将堆内存及时释放 delete []pScore; return 0; } 【例1-10】使用堆内存分配方式实现学生信 息录入功能,要求程序运行时由用户输入学 生人数。 #include <iostream> using namespace std; struct Student { char num[8]; char name[10]; int score; }; int main() { Student *pStu; int n, i; cout<<"请输入学生人数:"; cin>>n; //根据学生人数动态分配内存 pStu=new Student[n]; if (pStu==NULL)//判断堆内存分配是否成功 { cout<<"堆内存分配失败!"<<endl; return 0; } for (i=0; i < n; i++)//通过for循环输入学生信息 { cout<<"请输入第"<<(i+1) <<"名学生的学号、姓名和入学成绩:"; cin>>pStu[i].num>>pStu[i].name >>pStu[i].score; } for (i=0; i < n; i++)//通过for循环输出学生信息 cout<<"第"<<(i+1) <<"名学生的学号、姓名和入学成绩为:" <<(pStu+i)->num<<','<<(pStu+i)->name <<','<<(pStu+i)->score<<endl; delete []pStu;//不再使用时将堆内存及时释放 return 0; }在这里插入代码片<6>C++与C的主要差异——引用和返回引用的函数
引用就是别名,变量的引用就是变量的别名,对引用的操作就是对所引用变量的操作。 • 引用的声明形式为: • <数据类型> &<引用名>=<变量名>; • 建立引用时,必须用已知变量名为其初始化,表示该引用就是该变量的别名。 &是引用运算符,作用于引用名,表示紧随其后的是一个引用。例如,要同时定义两个int型引用r1和r2,必须写成如下形式: • int a,b; • int &r1=a, &r2=b;
如果写成: int &r1=a, r2=b; 则表示定义了一个引用r1和一个普通变量r2。 一个引用所引用的对象初始化后就不能修改。另外,引用就是一个别名,声明引用不会再为其分配内存空间,而是与所引用对象对应同一片内存空间。因此,对引用的操作与对所引用对象的操作效果完全一样。例如:
int a=5, b=10; int &r=a;//r是a的引用 r=b; //将b的值赋给r。因为r是a的引用,所以相当于将b的值赋给a
• 也可以为指针变量声明引用,其声明形式为: • <数据类型> *&<引用名>=<指针变量名>; • 在实际应用时,引用主要是用在函数中,一方面可以将函数的返回类型声明为引用,另一方面可以将函数的形参声明为引用。
返回引用的函数是指函数的返回值是return后变量的引用,返回引用的函数调用可以作为赋值语句的左值。
• 【例1-11】返回引用的函数示例。 #include <iostream> using namespace std; int array[5]={1, 2, 3, 4, 5}; int& index(int i); int main() { cout<<"赋值前,array[3]=" <<array[3]<<endl; index(3)=15; cout<<"赋值后,array[3]=" <<array[3]<<endl; return 0; } int& index(int i) { return array[i]; }• 提示: • (1)只有返回引用的函数可以作为赋值语句的左值。返回引用的函数通常用在类中。 • (2)在返回引用的函数中,可以返回全局变量或静态变量的引用,但不能返回局部变量的引用,因为局部变量的生存期只是在定义该局部变量的函数中,当函数调用结束时局部变量的内存空间会被释放,对已释放的内存空间进行引用可能会出现问题。
<7>C++与C的主要差异——函数的引用调用
• 将函数的形参声明为引用主要起到以下两方面的作用。 • 1. 通过引用调用更改实参变量的值 • 前面学习了函数的传值调用,在传值调用方式下,参数的传递为单向传值,即实参值传递给形参后,形参值在函数中的变化对实参值无任何影响。
【例1-12】函数的传值调用。 #include <iostream> using namespace std; void swap(int a, int b); int main() { int x=5, y=10; cout<<"交换前,x="<<x <<",y="<<y<<endl; swap(x, y); cout<<"交换后,x="<<x <<",y="<<y<<endl; return 0; } void swap(int a, int b) { int t=a; a=b; b=t; } 【例1-13】函数的引用调用。 #include <iostream> using namespace std; void swap(int &a, int &b); int main() { int x=5, y=10; cout<<"交换前,x="<<x <<",y="<<y<<endl; swap(x, y); cout<<"交换后,x="<<x <<",y="<<y<<endl; return 0; } void swap(int &a, int &b) { int t=a; a=b; b=t; }对自定义类型的变量,也可以通过引用方式传递: void StudentInfoInput(Student &stu) { cin>>stu.num>>stu.name>>stu.score; } 在调用时,直接将pStu[i]作为实参传递: StudentInfoInput(pStu[i]); 由于形参stu是实参pStu[i]的引用,因此在StudentInfoInput()函数中对形参stu所做的操作就是对实参pStu[i]的操作。
• 2. 通过引用调用提高函数调用效率 • 在调用函数时,需要将实参的值传递给形参。如果一个实参本身的数据量较大,则这个传递过程会消耗较长的时间。为了减少参数传递的时间开销,可以对一些数据量比较大的实参(如结构体变量或对象)采用引用调用方式。 • 当以引用调用方式传递实参,而在函数体中又不需要更改实参的值,则一般在引用形参中加上const关键字,使其成为const引用。使用const引用只能访问所引用对象的值,而不能修改所引用对象的值。const引用有两种声明形式: • const <数据类型> &<引用名>=<变量名或常量>; • 或 <数据类型> const &<引用名>=<变量名或常量>;
例如: int a=3; const int &r=a; r=10;//错误:不能通过const引用修改所引用对象的值
再如: void StudentInfoOutput(const Student &stu) { cout<<stu.num<<’,’<<stu.name<<’,‘<<stu.score<<endl; } 在调用时,直接将pStu[i]作为实参传递: StudentInfoOutput(pStu[i]);
• 另外,由于const引用不需要修改所引用对象的值,所以const引用与非const引用还有一个区别: const引用可以使用常量对其进行初始化,而非const引用则不可以。例如: • const int &r1=3; //正确:const引用可以使用常量对其进行初始化 • int &r2=3; //错误:非const引用不能使用常量对其进行初始化 • 引用调用和传值调用也可以混合使用,例如: • int fun(int &a, int b); • 其中,a是引用调用,b是传值调用。