1、面向对象的C++
c++是在C语言的基础上发展起来的一门语言,C++是即支持结构化程序编程又支持面向对象程序设计的混合型语言。他一方面提供了对C的兼容性,保持了C的简介、高效,接近汇编语言的特点,另一方面,C++引入了类、对象的概念,可以进行类的继承和派生,使C++成为一种面向对象的程序设计语言。早期的具有面向对象性能的程序语言存在很多缺点,如建立对象不凡便,与用户交互能力差等。C++克服了这些缺点,实现了真正的可视化编程。用户使用C++时,不用自己一一建立对象,只要在C++提供的框架内添加实现某种功能的代码即可。
2、C++对C语言的改进
c++与C语言兼容,C语言中的数据类型、运算符、表达式、函数定义和调用、预处理命令等在C++中都是适用的,还包括语句格式等。C++继承了C的风格和特点,但同时又对C的不足和问题做出了改进,主要包括:
(1)增加了一些新的运算符,使得C++应用起来更加方便,如::,new,delete,.*,->等。
(2)改进了类型系统,增加了安全性。C语言中类型转换很不严格,而C++规定类型转换大多采用强制转换,函数的说明必须使用原型,还对默认类型做了限制。
(3)增加了“引用”概念,是的引用函数参数更加方便。
(4)允许函数重载,允许设置默认参数,这些措施提高了编程的灵活性,还减少了冗余性。
(5)引进了内联函数的该概念,提高了程序的效率。
(6)对变量说明更加灵活,在满足先定义后使用的前提下,局部变量的定义和声明可以在程序块的任何位置。
3、C++的应用场景
C++并非万能药,这里举出一些C++的适用时机。
- C++适合构造程序中需求较稳定的部分,需求变化较大的部分可使用脚本语言;
- 程序须尽量发挥硬件的最高性能,且性能瓶颈在于CPU和内存;
- 程序须频繁地与操作系统或硬件沟通;
- 程序必须使用C++框架/库,如大部分游戏引擎(如Unreal/Source)及中间件(如Havok/FMOD),虽然有些C++库提供其他语言的绑定,但通常原生的API性能最好、最新;
- 项目中某个目标平台只提供C++编译器的支持。
按应用领域来说,C++适用于开发服务器软件、桌面应用、游戏、实时系统、高性
4、使用Visual Studio创建一个win32控制台应用程序
用Visual Studio不能单独编译一个.cpp或者一个.c文件,这些文件必须依赖于某一个项目,因此必须创建一个新项目,下面创建一个控制他应用程序。
点击确认后,即进入了如下界面
再输入程序如下:
#include "stdafx.h" #include "iostream" using namespace std; int main() { cout << "hello world! "; return 0; }
#include<iostream>为预处理命令,预处理器包含在编译器中。iostream为C++自带的库文件,它包含标准流cin、cout输入和输出,当程序中需要用到这些输入输出流对象时,要用#include将其包含在程序中。
第二行using namespace std;,该语句是说明程序使用C++标准库的命名空间std。命名空间是ANSI C++引入的可以由用户命名的作用域,用来处理程序中常见的同名冲突。这个再后面会进行讲解。
运行程序,可以得到如下结果:
·运行结果如下:
5、C++对C的扩充
1、C++的输入输出
C++本身没有专门的输入输出语句,C++的输出和输入不是C++本身定义的,而是编译系统提供的I/O库中定义的,是用“流”的方式实现的,使用的是iostream库。
(1)输出流cout
cout<<表达式1[<<表达式2<<..<<表达式n];
该语句功能是:依次输出表达式1、表达式2、...表达式n的值。具体输出内容可以是一个整数、实数、字符及字符串。虽然cout不是C++本身提供的语句,但为了方便,常常由cout和流插入运算符<<实现输出的语句称为输出语句或cout语句。下面是一个实例:
#include "stdafx.h" #include "iostream" using namespace std; int main() { cout << "this is a c++ program"<<endl; }
在执行该语句是,系统先把插入的数据顺序存放在输出缓冲中,直到输出缓冲区满或遇到cout语句中的endl(或' ',ends,flush)为止。将缓冲去中已有的数据一起输出,并清空缓冲区。
endl表示输出一个换行字符,同时刷新流,如果不用endl,还可以使用转移字符‘ '来表示换行。
其中使用cout语句需要注意以下几点:
- 一个插入运算符<<只能插入一个输出项,多个输出项时要有多个插入运算符<<,例如,要输出变量a、b、c,不能写成cout<<a,b,c。而应写成cout<<a<<b<<c。
- cout输出时,用户不必通知计算机以何种类型输出,系统会自动判别输出数据的类型,使输出的数据按相应的类型输出。即使定义变量a、b、c时是不同类型的,但输出是可以直接写如下语句:cout<<a<<' '<<b<<endl
- 一条语句包含多个输出项时,可以分成若干行书写,也可以用多条语句输出。
例如:cout<<"this is a C++ program"<<endl可以写成:
cout << "this " << "a C++" << "program" << endl;
或者:
cout << "this "; cout << "a C++"; cout << "program"; cout << endl;
(2)、输入流cin
cin语句一般格式
cin>>变量>>[变量>>...>>变量n];
该语句功能为:运行程序时从键盘输入变量1、变量2、...变量n的值。
使用该语句需要注意以下几点:
- 一个提取运算符>>只能插入一个输入项,如果有多个输入项,要用多个提取运算符>>
运行程序是,从键盘上输入多个变量,多个值之间用空格、Tab或回车键分开。
- cin与cout类似,系统也会根据变量的类型从输入流中提取相应长度的字节。
例如:
// example1.cpp: 定义控制台应用程序的入口点。 // #include "stdafx.h" #include "iostream" using namespace std; int main() { char c1, c2; int a; float b; cin >> c1 >> c2 >> a >> b; }
需要注意的是不能用cin语句把空格字符和回车换行符作为字符输入给字符变量,他们将被跳过。如果想将空格字符或者回车换行符输入给字符变量,可以使用getchar()函数。
- 与cout类似,一个cin语句可以分写成若干行。
(3)、格式控制
利用格式控制符可以进行格式化的输入和输出。用oct、dec、hex分别将输入或输出的数值换成8进制、10进制及16进制。例如:
cout << oct << a << endl;//输出a的8进制 cout << dec << a << endl;//输出a的10进制 cout << hex << a << endl;//输出a的16进制
此外,还有很多格式控制符,例如:
ws:输入流的时候删掉空白字符。
ends:输出一个null字符。
endl:输出一个换行字符,同时刷新流。
flush:刷新流。
下面通过一个实例加深了解
// example1.cpp: 定义控制台应用程序的入口点。 // #include "stdafx.h" #include "iostream" using namespace std; int main() { char a[20]; cin >> a; cout << a << endl; return 0; }
这里发现,输入aaa bbb输出却只为aaa,这是因为遇见空格后就结束了,后面BB无法读取出来,所以输出的是aaa.
2、变量的存储类型
1、变量的作用域和生存期
变量的作用与即变量的作用范围。有的变量可以在整个程序或其他程序中使用,有的则只能在局部范围内使用。按作用域范围范围可以将变量分为两种:局部变量和全局变量。
变量的生存期是指变量从生成到被撤销的这段时间。实际上就是变量占用内存的时间。按生存期长短可将变量分为两种:动态变量和静态变量。
变量只能在其生存期里被引用,变量的作用域直接影响变量的生存期。作用域和生存期是从空间和时间的两个不同的角度来描述变量的特性。
- 局部变量的作用域和生存期
在一个函数内部定义的变量就是局部变量,其作用域只在本函数范围内有效,也就是说只有在本函数内才能使用他们,在此函数之外是不能使用这些变量的,这是因为函数内的变量是保存在栈内的,而所有栈内的内容在函数调用结束后会被清除。所以,局部变量的生存期是从函数被调用的时刻开始到函数返回调用处的时刻(静态变量除外)结束。在使用局部变量时,需要注意以下几点:
- 主函数main()中定义的变量也是局部变量,它只能在主函数中使用,其他函数不能使用。同时,主函数中也不能使用其他函数中定义的局部变量。
- 形参变量属于被调用函数的局部变量;实参则属于全局变量或调用函数的局部变量。
- 允许在不同的函数中使用相同的变量名,他们代表不同的对象,分配不同的单元,互不干扰,也不会发生混淆。
- 在复合语句中定义的变量也是局部变量,其作用域只在符合语句范围内。其生存周期是从复合语句被执行的时刻到复合语句执行完毕的时刻。
- 在函数声明中出现的参数名,其作用范围只在本行的括号内。实际上,编译系统对函数中声明的变量是忽略的,即使在调用函数时也没有为它们分配存储内存。例如:
int max(int a, int b);//函数声明a、b int max(int x, int y)//函数定义,形参是x、y { cout << x << y << endl;//合法,x、y在函数内有效 cout << a << b << endl;//非法,a、b在函数体中无效。 }
编译时,系统认为函数体中的a未经定义。
2.全局变量作用域和生存期
在函数外部做定义说明的变量,称为外部变量。它属于整个源程序文件。这是因为全局变量是保存在堆中的,堆内的数据可以从程序开始运行一直到程序运行结束。其作用域从定义变量的未知开始到源文件结束,或者是有extern说明的其他源文件。全局变量的生存期和程序相同。使用时需要注意以下几点:
- 应尽量少使用全局变量,因为全局变量在程序执行过程中始终占用内存单元,降低了函数的独立性、通用性、可靠性及可移植性,降低了程序的清晰性,容易出错。
- 若全局变量与局部变量同名,则全局变量被屏蔽。要引用全局变量,则必须在变量名前加上两个冒号::。
- 全局变量定义必须在所有的函数之外,且只能定义一次,并可赋初始值。全局变量定义的一般形式为:
[extern] 类型说明符全局变量名1[ =初始值1],...,全局变量名n[ =初始值n];
- 对全局变量进行声明,可扩展全局变量的作用域。全局变量说明的一般形式为:
extern 类型说明符 全局变量名1,...,全局变量名n;
2、变量的存储类型
在C++中变量除了有数据类型的属性之外,还有存储类别的属性。存储类别指的是数据在内存中的存储方法。存储方法分为静态存储和动态存储两大类。具体包含四种:自动(auto)、静态的(static)、寄存器的(register)和外部的(extern)。
考虑了变量的存储类型后,变量定义的完整形式应为:
存储类型说明符 数据类型说明符 变量名1,变量名2,...变量名n; 例如: auto char c1,c2; register i; static int a,b; extern int x,y;
- 自动变量
程序中大多数变量属于自动变量。函数中的局部变量,如果不用关键字static加以声明,编译系统对它们是动态的分配存储空间的。函数的形参和在函数中定义的变量(包括在符合语句中定义的变量)都属于此类。在调用该函数时,系统给形参和函数中定义的变量分配存储空间,数据存储在动态存储区中,在函数调用结束后自动释放这些空间。如果是在复合语句中定义的变量,则在变量定义时分配存储空间,在符合语句结束时自动释放空间。如果在符合语句中定义的变量,则在变量定义时分配存储空间,在复合语句结束时自动释放空间。因此者类局部变量称为自动变量。自动变量用关键字auto作存储类别的声明。
例:
int f(int a) //定义f函数,a为形参 { auto int b, c = 3; //定义b和c为整型的自动变量 }
存储类型auto与int的顺序是任意的,而且关键字auto还可以省略,如果不写auto,则系统默认为自动存储模式,它属于动态存储方式。
注意,用auto、register、static声明变量时,是在定义的基础上加上这些关键字,而布恩那个单独使用。
2.静态变量
静态变量在整个程序生命周期内,其地址静止不变。
静态变量的类型说明符是static。静态变量属于静态存储类型,但静态存储类型的变量不一定是静态变量。例如,外部变量虽属于静态存储类型,但不一定是静态变量,必须用static加以定义后才能称为静态外部变量。
全局变量改变为静态变量后会改变它的作用域,限制了它的使用范围。当一个源程序由多个源文件组成时,非静态的全局变量可通过外部变量说明使其在多个文件中都有效。而静态全局变量只在定义该变量的源文件内有效,在同一项目的其他源文件中不能使用。
自动变量可以用static改变为静态自动变量,改变后,其生存周期为整个源程序,但是作用域与自动变量相同。此外需要注意的是,静态局部变量赋字操作只运行一次,此后再调用,不再进行赋值。
例:
#include "stdafx.h" #include "iostream" using namespace std; int main() { int i; void func(); //函数说明 for (i = 1; i <= 5; i++) func(); //函数调用 return 0; } void func() //函数定义 { static int j = 1; //静态局部变量,其只运行一次。 ++j; cout << j << ""; }
可以看到,五次调用函数func,但是赋值给j=1的操作只执行了一次。
3.寄存器变量(register型变量)
一般情况下,变量的值是存放再内存中的,当程序中用到哪一个变量的值时,由控制器发出指令将内存中该变量的值送到CPU的运算器中,而寄存器变量存放在CPU的寄存器中,使用时,不需要访问内存,而是直接从寄存器中读写,这样可提高效率。
寄存器变量的说明符为register,属于动态存储类型。只有局部自动变量和形式参数才可以定义为寄存器变量。
4.外部变量
外部变量(即全局变量)是在函数的外部定义的,它的作用域为从变量的定义开始,到本程序文件的末尾。在此作用域内,本文件的各个函数都可以引用全局变量。编译时将全局变量分配到静态存储区。
如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件结束。如果在定义之前想引用该全局变量,则应该在引用之前用关键字extern对该变量做外部变量声明,表示该变量是一个在后面定义的全局变量。有了此声明,就可以从声明处起,合法的引用该全局变量,这种声明称为提前引用声明。如果程序由多个文件组成,在一个文件中定义的外部变量,在另一个文件中对该外部变量进行声明后,也可以合法的引用该外部变量。
用extern扩展全局变量的作用域,虽然给程序带来了方便,但会使程序的可读性变差,修改不便,使用时要慎重。
3、函数的默认参数
在C和C++中使用函数时,包括函数的声明、定义、调用三部分,都要遵守相应的规则。
1、函数的声明
函数声明就是把函数的名字、函数类型以及形参的个数、类型和顺序通知编译系统,以便在遇到函数调用时,核查调用形式是否与声明相符。
函数声明的一般形式:
函数类型 函数名(参数类型1 参数名1,参数类型2 参数名2,...); 函数类型 函数名(参数类型1,参数类型2..);
第二种声明是对第一种声明的简化,它省略了参数名,因为函数声明不涉及函数体,所以编译系统不关心参数是什么。
2、函数的定义
函数定义是指对函数功能的实现,包括指定函数名、函数类型、形参及其类型、函数体等,他是一个完整的、独立的函数单位。
函数定义的一般形式:
函数类型 函数名(形式参数表) { 声明部分; 函数体主体; return 返回值; }
3、函数的调用
使用该函数,叫做函数调用,函数调用方式是:
函数名(实际参数表)
只要函数声明出现在函数调用之前,就可以把包含函数体的函数定义移到函数调用的后面。因此,在程序中调用函数由以下两种方式:
方式1: 函数声明; 函数调用; 函数定义; 方式2 函数定义; 函数调用;
例如之前的例子,还可以这样写:
#include "stdafx.h" #include "iostream" using namespace std; void func() //函数定义 { static int j = 1; //静态局部变量,其只运行一次。 ++j; cout << j << ""; } int main() { int i; for (i = 1; i <= 5; i++) func(); //函数调用 return 0; }
4、函数的默认参数
C++允许给函数形参赋予默认值。所谓默认值就是在调用时,可以不必给出某些参数的值,编译器会自动把默认值传递给调用语句。对于函数的形参,可以给出默认值,也可以不提供默认值,还可以指对形参的一部分给出默认值。默认值在函数参数较多时是非常有用的。可以只传必需的值。
使用默认参数时,需要注意以下几点:
- 默认参数设置位置
参数的默认值可以在声明中或定义中设置,但只能在其中一处设置,不允许在两处同时设置。如果函数的定义在函数调用之后,则只能在函数声明中设置默认参数。因为此时如果在定义中设置,编译器不知道哪个参数设置了默认值。
- 带默认参数的函数调用
设置了默认值的参数,函数调用是可以不再给值,直接读取默认值,也可以不区默认值,重新赋值。
例如:
#include "stdafx.h" #include "iostream" using namespace std; int add(int a, int b = 5); //函数声明 int main() { int a = 1; int b = 2; cout<<add(a, b) << endl; //函数调用,第二个参数没有取默认值 cout << add(a) << endl; //函数调用,第二个参数取默认值 return 0; } int add(int x,int y) //函数定义 { int z; z = x + y; return z; }
输出结果为:
- 默认参数的顺序规定
如果一个函数中有多个默认参数,则默认参数应从右到左逐渐定义。即当某个参数是默认参数,那么它后面的参数必须都是默认参数。例如:
int add(int a,int b,int c=1); //true int add(int a=1,int b=1,int c=1); //true int add(int a=1,int b=1,int c); //false
当调用函数时 ,传进去的实参个数必须大于或等于无默认值的形参的个数,匹配参数时是从左至右去匹配。例如,对三个参数都是默认参数的,正确的调用格式为:
add() //三个参数都取默认值1,函数值为3 add(2,3) //a=2,b=3,c取默认值1,函数值为6 add(3,4,5)//a、b、c都不取默认值,结果为12
- 参数默认值的限定
在前面的例子中,参数默认值都是常量,实际上,默认值可以是全局变量,甚至是一个函数调用。
例如
int m = 1; //m为全局变量 int fun(int i = m); //正确,参数默认值为全局变量m int add(int x; int y = fun()); //正确,add函数的参数默认值为fun()函数值,而且,fun()函数调用使用的是参数默认值。
但默认值不能是局部变量,因为默认参数的函数调用是在编译时确定的。
4、函数的重载
函数编程过程中,经常会遇到这种情况,就是需要编写若干个函数,他们的功能相似,但是参数不同,可以统一给这些函数取一个相同的名字,但设置不同的参数,编译系统在函数调用时能够将各个函数区分开来。如果两个函数名字相同并且在相同的域中被声明,但是参数表不同,那么他们就是重载函数。
重载函数必须是参数类型或参数个数不同。使用重载函数需要注意:
- 重载函数都应在一个域中被声明,不同域中声明的同名函数不是重载函数。
- 只有函数的返回类型不同,参数相同的不是重载函数。因为函数调用时系统无法根据函数返回类型确定执行哪个函数,因此会在编译时认定为是重复定义。
- 不要把功能不同的函数放在一起重载。这样会破坏程序的可读性和可理解性。
- 如果有函数重载,同时有些函数的形参带默认值时,这样有可能引发歧义,编译系统无法确定调用哪个函数,因而产生错误。
函数重载要求编译器能够唯一的确定调用一个函数应执行哪个函数代码,即采用哪个函数实现。进行函数重载时,要求同名函数在参数个数上不同,或者参数类型上不同,否则,将无法进行重载。
例:编写两个重载求和函数,一个计算两个整数的和,一个计算两个浮点型数的和。
#include "stdafx.h" #include "iostream" using namespace std; int add(int x, int y); //函数声明 double add(double a, double b); int main() { cout<<add(2, 3) << endl; //函数调用,第二个参数没有取默认值 cout << add(2.2,3.3) << endl; //函数调用,第二个参数取默认值 return 0; } int add(int x,int y) //函数定义 { return x + y; } double add(double a, double b) //函数定义 { return a + b; }
5、内联函数
内联函数是C++引进的新概念,在C语言中没有。内联函数具有一般函数的特性,他与一般函数的不同之处只在于函数调用的处理。一般函数进行调用时,要将程序执行权转到被调用函数中,执行完被调用函数后才再次返回到调用它的函数中;而内联函数是在编译时直接将内联函数的函数体代码嵌入到调用函数中,所以内联函数被执行时,不涉及到流程的转出和返回,也不涉及到参数传递,提高了执行效率。
内联函数是在函数声明或函数定义是,在函数名前加一个inline。示例如下
inline int add(int x,int y) //函数定义 { return x + y; }
使用内联函数有以下注意事项:
- 如果一个函数被指定为inline函数,则它将在函数中每个调用点上被内联的展开,使用内联函数是一种以空间换时间的行为,所以函数内语句较长或包含复杂的控制语句,如循环语句、if语句或switch语句或递归时,不宜使用内联函数。
- 关键字inline必须与函数定义放在一起才能使函数称为内联,仅仅放在函数声明前起不到任何作用。
- 并不是所有加了inline的函数都是内联函数,inline对于编译器来说只是一个建议,编译器可以选择忽略该建议,自动进行优化。所以当inline中出现递归、循环或者过多代码时,编译器自动将其作为普通函数调用。
- 在C++类中,类体内的成员函数自动被当成内联函数,应用非常广。
- 当编写复杂的应用程序时,内联函数的定义要放在头文件中,如果其他文件要调用这些内联函数的时候,只要包含这个头文件就可以了。
6、引用和引用传递
1、引用的定义
引用是C++中提供的一个新概念,它与指针密切相关。引用是一个变量的别名,定义引用类型变量,实质上是给一个已定义的变量起一个别名,系统不会为引用类型变量分配内存空间,只是使引用类型变量和其相关联的变量使用同一个内存空间。
定义引用类型变量的一般格式为:
<数据类型> &<引用名>=<变量名> //&不是取地址符,是引用的标识 或 <数据类型> &<引用名> (变量名)
例如:
int a=3; int &ra = a;
这里,ra就是一个引用,他是变量a的别名。引用ra和变量a不仅值相同,地址也相同。对引用进行的计算,例如:ra=ra+2;实质上是a加上2,a的结果为5.
使用引用的注意事项如下:
- 引用必须初始化,因为它只是某个变量的别名,只能在定义引用的同时给它赋值。
- 除了初始化,引用不能再赋新值,即引用再其整个生命周期中是不能被改变的,只能依附于同一个变量。
- 不能建立数组的引用,因为数组名表示的是一组数据的起始地址,它自己不是一个真正的数据类型。
- 不能对引用再进行引用。
下面通过一个实例加深了解。
#include "stdafx.h" #include "iostream" using namespace std; int main() { int a; int &ra = a;//将ra引用为a cout << "a=" << a << endl; cout << "ra=" << ra << endl; cout << "address of a is :" << &a << endl; cout << "address of ra is :" << &ra << endl; int b = 8; ra = b; cout << "a=" << a << endl; cout << "b=" << b << endl; cout << "ra=" << ra << endl; cout << "address of a is :" << &a << endl; cout << "address of b is :" << &b << endl; cout << "address of ra is :" << &ra << endl; return 0; }
运行结果:
2、引用传递
引用传递是指将引用作为函数参数来实现的函数参数的传递。
一般的,函数形参为一般变量,调用时实参与形参之间参数传递只能是从实参到形参,是单向的。从被调用函数的角度来说,参数的值只能传入,不能传出,也就是通常的值传递。当用指针作为函数参数,调用时将实参的地址初始化成形参的指针,则可以实现实参和形参的双向传递,即地址传递。
引用传递是指引用作为函数的形参,当调用函数是,对应的形参就是相应实参的别名。在调用函数内对形参的修改就是对实参的修改;在调用函数外对实参的修改,当进入被调用函数内时,相应的形参就是已经修改的实参,实现了参数的双向传递。
例:利用自定义函数交换两个变量的值,要求用引用作为函数形参。
下面程序中自定义函数的形参为引用。
#include "stdafx.h" #include "iostream" using namespace std; void swap(int &a, int &b) { int temp; temp = a; a = b; b = temp; cout << a << " " << b << endl; } int main() { int x = 1; int y = 2; cout << x << " " << y << endl; swap(x, y); cout << x << " " << y << endl; return 0; }
这里,形参实参间采用的是引用传递,被调函数的形参谁然也是局部变量,但是它存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接的寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都会影响主调函数中的实参变量。
3、引用作为函数返回值
函数定义时,函数名前加&号,可以将引用作为函数返回值,当使用引用作为返回值时需要注意以下几点:
- 不能返回局部变量或临时变量的引用,但可以返回全局变量的引用,也就是说要注意被引用的对象不能超出作用域。
- 不能返回函数内部动态分配的内存的引用。因为被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的由new分配的空间就无法被释放,从而造成内存泄漏。
- 可以返回类成员的引用,但最后是cost常量。这是因为当对象的属性是与某种业务规范相关联的时候,其赋值常常与某些其他属性或者对象的状态有关,于是有必要将赋值操作封装在一个业务规则中。如果其他对象可以获得该属性的非常量引用,那么对该属性的单纯赋值会破坏业务规则的完整性。
#include "stdafx.h" #include "iostream" using namespace std; double arr[5] = { 1,2,3,4,5 }; double &change(int i) { return arr[i]; } int main() { int i; cout << "origin data is:"; for (i = 0; i < 5; i++) cout << arr[i] << " "; cout << endl; change(2) = 3.14; change(3) = -97; cout << "Modified data is:"; for (i = 0; i < 5; i++) cout << arr[i] << " "; cout << endl; return 0; }
7、用const定义常变量
用const类型修饰符说明的类型称为常类型,常类型的变量或对象的值是不能被更新的。使用const来定义的常量,具有不可以变性,可以避免意义模糊的数字出现,方便进行参数的调整和修改,提高执行效率,便于进行类型检查,是编译器对处理内容有更多了解,消除隐患。
1、用const修饰一般变量
通过const关键字将一个变量定义为常量。例如:const int a=10;,如果在程序中试图修改a的值,则会引起一个错误。由于const类型的量一经定义就不能改变它的值,因此在定义时必须初始化。用const定义常变量格式为:
const 数据类型 变量名 = 表达式; const int a=10; 或 数据类型 const 变量名 = 表达式;
int const a=10;
2、const修饰指针变量
用const修饰指针变量,不同的写法会有不同情况。下面举例说明:
(1)const int *pi和int const *pi的意义相同,表示const修饰的类型为int的指针变量pi为常量,因此,pi的内容为常量且不可变。即指针所指向的内容是常量不可变,语句等价为const(int) *pi和(int) const *pi;
(2) char *const pi等价于const (char*) pi,表示const修饰的类型为char*的变量pi为常量,因此,pi指针本身为常量不可变。
(3)const char* const pi 表示指针本身和指针内容两者皆为不可变的。
3、const限定函数参数
const可以用来限定函数参数,例如:
void Fun(const int Var);
被限定的参数在函数体中不可变,由值传递的特点可知,即使Var在函数体中被改变了,也不会影响到函数外部。所以此限定与函数的使用者无关,仅与函数的编写者有关。
4、const限定函数的值型返回值
const可以用来限定函数返回值,例如:
const int Fun2()
它表示被限定函数的返回值不可被更新,当函数返回值是内部的类型时,已经是一个数值,不可被赋值更新。此时无意义,最好去掉。但是这个例子说明了,当函数返回值是自定义的类型时,这个类型仍然包含可以被赋值的变量成员,用const可以限定返回值不能更改。
5、const限定类、对象、对象引用
const修饰类、对象时表示该对象为常对象,其中的任何成员都不能别修改。对于对象指针和对象引用也是一样。const修饰的对象,该对象的任何非const成员函数都不能被调用,因为任何非const成员函数都会有可能修改成员变量。
8、字符串变量
在C语言中,没有字符串型的数据类型,需要字符串类型变量时是通过字符数组或字符指针来实现的。在C++中除了上述方法外,还可以通过包含头文件string,然后用string来实现字符串变量的定义。string是在C++标准库中声明的一个字符串类,用该类可以定义对象,每一个字符串变量都是string类的一个对象实例。使用时,可以将string看作一个新的数据类型——字符串类型,用它定义的变量就称为字符串变量。
1、字符串变量的定义
和其他类型变量一样,字符串变量也必须先定义后使用,定义字符串变量要用类名string。格式为:
string 变量1[,变量2,...变量n]
如果对变量初始化,则格式为:
string 变量名1=字符串表达式1[,变量名2=字符串表达式2,....变量名n=字符串表达式n
示例
string string1; string string2="chian";
2、字符串变量的赋值
在定义了字符串变量后,可以进行赋值,格式为:
字符串变量=字符串表达式;
根据字符串表达式的不同,分为以下三种情况:
- 字符表达式可以说字符串常量,例如:string1=“canada";
- 字符表达式是另一个字符串变量。例如:string2=string1;只要string1和string2均为字符型变量即可。
- 字符串表达式可以是由运算符、常量、变量组成的表达。例如:string2=”hello“+string1;
3、字符串变量的输入输出
可以在输入输出语句中用字符串变量名,直接进行字符串的输入输出。例如:
cin >> string; //从键盘输入要给字符串给字符串变量string1 cout << string2; //输出string2
另外,定义一个字符串变量后,也可以将其当作一个字符数组使用,字符串变量名即为字符数组名;例如
string word="hello" cout<<word[0];
3、字符串变量的运算
用字符串数组表示字符串变量时,其运算必须用专门的字符串函数。而用string定义的字符串变量可以直接使用简单的运算符进行运算。例如,用赋值好进行字符串复制,用加号进行连接,用关系运算符进行字符串比较等。
#include "stdafx.h" #include "iostream" #include <string> using namespace std; int main() { string name; cout << "please enter your name!" << endl; cin >> name; cout << endl << name + "welcome to C++ wrold!" << endl; return 0; }
9、内存动态分配与撤销运算符new和delete
用new和delete可以动态开辟、撤销地址空间。
1、new及其用法
在程序运行时,用new运算符动态分配内存后,将返回一个指向新对象的指针,即所分配的内存地址空间的起始地址。用户可以通过这个地址,并用这个地址来访问对象。如果分配失败则返回0,通常将地址赋值给一个指针变量。一般格式为:
类型名 *指针变量= new 类型名; 类型名 *指针变量= new 类型名(初值);//对存入该地址的数据初始化
要注意的是类型名即可是int、char等基本数据类型,也可以是结构体、类等用户自定义的类型;用new既可以给单个变量动态分配内存,也可以给数组动态分配内存。
- 单变量的动态内存分配
例如:
int *pa; pa= new int ; *pa=8;
该语句的作用是,首先定义一个整型指针变量pa,用new动态分配用于存放指针变量pa,将首地址存入指针变量pa,并将数值8存入该地址。
以上三条语句可以合并为以下一条
int *pa= new int(8);
- 数组内存动态分配
一般格式为:
类型名 *指针变量= new类型名[数组大小] 例如: int *ps; ps=new int[10];
该语句的作用是:首先定义要给整型变量ps,用new动态分配用于存放一个包含10个元素的整型数组的内存空间,将首地址存入变量ps。
2、delete及其用法
动态内存的生存周期由程序员决定,如果不及时释放内存,程序将在最后才释放掉动态内存。一般的,如果某动态内存不再使用,需要将其释放掉,否则,会发生内存泄漏现象。delete和new要成对使用。用delete释放内存空间的语句格式为:
delete 指针名; //用于释放单变量 delete []指针名; //用于释放数组 例如 delete pa; delete []ps;
10、命名空间
在单人完成程序时,只要小心一点,是可以不发生重名的。但是一个大项目往往不是一个人完成的。由于相同的语言规则,那么不同人完成过程中,类名、全局函数名、全局变量名、重复是很常见的事,尤其是许多程序员合作并可能调用第三方库的时候,如果没有命名空间或者类似机制的话,项目在实体命名讨论时就要花费大量时间,为了解决命名冲突,C++提出了命名空间的概念。它实际上是一个由程序设计者命名的内存区域,程序设计者可以根据需要制定一些有名字的空间域,把一些全局实体分别放在各个命名空间中,从而与其他实体分隔开来。就好比两个函数或类中定义相同名字的对象一样,利用作用域标识符”::”加以区分。
对于命名空间的使用,C++标准程序库中所有标识符都被定义在一个名为std的命名空间中。使用C++标准库的某个标识符时,可以有如下三种方法:
- 使用作用域运算符直接指定标识符。例如:
std::cout<<std::hex<<3.4<<std::endl;
- 使用using声明来表示:
using std::cout; using std::endl; cout<<"hello world!"<<endl
- 使using namespace std编译命令,例如:
#include <iostream> using namespace std;
采用这种格式,命名空间std内定义的所有标识符都可以直接使用,就好像他们被声明为全局变量一样。
使用命名空间std的方法很多,但不能贪图方便,重视用第三种,这样就失去了命名空间设计的意义。一般情况下,按照以下原则选择使用方式:
- 对偶尔用到的命名空间成员,应该使用第一种方法
- 对于较大命名空间中的经常使用的少数几个成员,提倡使用第二种方法。
- 对于需要反复使用同一个命名空间的多个成员,使用using编译命名,即第三种方法。
在C++语言中,命名空间使用namespace来声明,并使用{}来界定命名空间的作用域。例如
namespace foo {int num=0}
6、总结示例
实例1、对已定义的下列变量:char str[20]="hello world!";int n=12;float f=1.234;写出鞋类要求的输入、输出语句:
- 输出字符串str
- 输出字符串str的地址。
- 以科学计数法显示f
- 使科学计数法的指数字母以大写输出
- 以8进制输出n
- 输出整数时显示基数
- 设置显示宽度为10,填充字符为“*”,右对齐方式显示。
- 分别设置精度为2、3、4显示f
- 按16进制输入整数,然后按10进制输出。
- 从流中读取10个字符到str,遇到!字符停止操作。
程序实现如下:
// example1.cpp: 定义控制台应用程序的入口点。 // #include "stdafx.h" #include<iostream> #include<iomanip>//主要是cin,cout之类的一些操纵运算子的头文件 using namespace std; int main() { char str[] = "hello world!"; int n = 12; float f = 1.234; cout << str << endl; cout << (long)str << endl; //setiosflags 意思就是设置输入输出的标志 /* setioflags(ios::fixed) 固定的浮点显示 setioflags(ios::scientific) 指数表示 setiosflags(ios::left) 左对齐 setiosflags(ios::right) 右对齐 setiosflags(ios::skipws 忽略前导空白 setiosflags(ios::uppercase) 16进制数大写输出 setiosflags(ios::lowercase) 16进制小写输出 setiosflags(ios::showpoint) 强制显示小数点 setiosflags(ios::showpos) 强制显示符号 */ cout << setiosflags(ios::scientific) << f << endl; cout << setiosflags(ios::uppercase) << f << endl; /* dec 置基数为10 相当于"%d" hex 置基数为16 相当于"%X" oct 置基数为8 相当于"%o" */ cout << oct << n << endl; //setfill设置填充字符 //setw(int n)只是对直接跟在<<后的输出数据起作用,n是在输出时分配了n个字符的输出宽度 cout << setfill('*') << setw(10) << setiosflags(ios::right) << n << endl; //setprecision功能是控制输出流显示浮点数的有效数字个数 cout << setprecision(2) << f<<endl; cout << setprecision(3) << f<<endl; cout << setprecision(4) << f<<endl; cin >> hex >> n;//输入然16进制数 cout << dec << n << endl;//以十进制输出n //getline会生成一个包含一串从输入流读入的字符的字符串,直到以下情况发生会导致生成的此字符串结束。1)到文件结束,2)遇到函数的定界符,3)输入达到最大限度 cin.getline(str, 10, '!'); return 0; }
运行结果如下:
实例2、输入一行字符,分别统计其中英文字母、空格、数字字符和其他字符的个数。要求从键盘上输入一个字符给变量c,知道输入回测换行字符“ 为止。
#include "stdafx.h" #include<iostream> using namespace std; int main() { char c; int letter = 0, number = 0, blank = 0, other = 0; cout << "请输入字符串:"; cin >> c; while (c != ' ') { if ('a' <=c&&c <= 'z' || 'A' <= c && c <= 'Z') letter++; else if ('0' <= c && c <= '9') number++; else if (c == ' ') blank++; else other++; cin.get(c); } cout << "lettter=" << letter << endl; cout << "number=" << number << endl; cout << "blank=" << blank << endl; cout << "other=" << other << endl; }
运行结果如下:
实例3:编写自定义函数,求三个整数的最大值。在此基础上,再将函数修改为带默认参数的函数,可以分别改为一个默认参数,两个默认参数,三个默认参数。
#include "stdafx.h" #include<iostream> using namespace std; int max(int a, int b, int c) { if (b > a)a = b; if (c > b)a = c; return a; } int main() { int i1, i2, i3, i; cin >> i1 >> i2 >> i3; i = max(i1, i2, i3); cout << i1 << "," << i2 << "," << i3 << "中最大值为" << i << endl; }
4、求三个整数、浮点数或双精度数的最大值,要求使用重载函数。
#include "stdafx.h" #include<iostream> using namespace std; int max(int a, int b, int c) { int temp = (a > b) ? a : b; if (temp > c) return temp; else return c; } double max(double a, double b, double c) { double temp = (a > b) ? a : b; if (temp > c) return temp; else return c; } float max(float a, float b, float c) { float temp = (a > b) ? a : b; if (temp > c) return temp; else return c; } int main() { float i1, i2, i3, i; cin >> i1 >> i2 >> i3; i = max(i1, i2, i3); cout << i1 << "," << i2 << "," << i3 << "中最大值为" << i << endl; }
5、定义一个函数,比较两个数的大小,形参分别用指针和引用。
#include "stdafx.h" #include<iostream> using namespace std; int max(int *a, int *b) { if (*a > *b) return *a; else return *b; } int max2(int &a, int &b) { if (a > b) return a; else return b; } int main() { int i1, i2, i3, i; cin >> i1 >> i2; i = max2(i1,i2); cout << i1 << "," << i2 << "中最大值为" << i << endl; }
6编写用new和delete进行动态内存分配和释放的程序。
#include "stdafx.h" #include<iostream> using namespace std; int main() { int *p = new int; *p = 8; cout << *p<<endl; delete p; return 0; }
运行结果如下: