-
引用
int n = 4;
int &r = n;
上面的写法就定义了一个引用,并将其初始化为引用某个变量。
类型名 &引用名 = 某变量名
上面指的是r引用了n,而r的类型时int &.
某个变量的引用,等价于这个变量,相当于该变量的别名。
定义引用时,一定要将其初始化成引用某个变量,初始化后,它就一直引用该变量,不会再引用别的变量了。
引用只能是引用变量,不能引用常量和表达式。
-
引用的应用例子
C语言编写交换两个整型变量的值用指针实现,有了C++中的引用后,可以这样实现:
//C语言交换变量的值
void swap(int *a, int *b){
int temp;
temp = *a;
*a = *b;
*b = temp;
}
int n1, n2;
swap(&n1, &n2);
//C++的引用
void swap(int &a, int &b){
int temp;
temp = a;
a = b;
b = temp;
}
int n1, n2;
swap(n1, n2);
从上面的例子可以看出,使用引用后调用swap函数的参数表更加简单了。
-
引用作为函数的返回值
int n = 4;
int & SetValue(){
return n;
}
int main(){
SetValue() = 40;
cout << n;
return 0;
}//最后输出结果为 40
-
常引用
定义引用时,前面加const关键字,即为“常引用”。
int n;
const int & r = n;
则r的类型是const int &
不能通过常引用去修改其引用的内容。
-
常引用和非常引用的转换
const T &和T & 是不同的类型
-
const关键字的用法
定义常量:const int MAX_VAL = 23;
const string SCHOOL_NAME = "pk";
定义常量指针:不可以通过常量指针修改其指向的内容。
int n, m;
const int *p = &n;
*p = 5; //编译出错
n = 4; //正确
p = &m; //正确,常量指针的指向可以变化
不能把常量指针赋值给非常量指针,反过来可以。
const int *p1; int *p2;
p1 = p2; //ok
p2 = p1; //error
p2 = (int *)p1; //ok,强制类型转换
函数参数为常量指针时,可避免函数内部不小心改变参数所指向的内容。
void MyPrintf(){
strcpy(p, "this"); //编译出错
printf(“%s”, p); //ok
}
-
定义常引用
不能通过常引用修改其引用的变量。
int n;
const int &r = n;
r = 5; //error
n = 4; //ok
-
动态内存分配
在C++中用new运算符实现动态内存分配。
第一种用法:分配一个变量P = new T.
T是任意类型名,P是类型为T*的指针,动态分配出一片大小为sizeof(T)字节的内存空间,并且将该内存空间的起始地址赋值给P,比如:
int *pn;
pn = new int;
*pn = 5;
第二种用法:分配一个数组P = new T[N];
T是任意类型名,P是类型为T*的指针。N是要分配的数组元素的个数,可以是整型表达式。
动态分配出一片大小为sizeof(T)*N字节的内存空间,并且将给内存空间的起始地址赋值给P。
int *pn;
int i = 5;
pn = new int[i*20];
pn[0] = 20;
pn[100] = 30; //编译时没问题,运行时导致数组越界
-
delete
用new动态分配的内存空间,一定要用delete运算符进行释放。
delete 指针(给指针必须指向new出来的空间)
int *p = new int;
*p = 5;
delete p;
delete p; //导致异常,一片空间不能被delete多次
用delete释放动态分配的数组,要加[]
delete [] 指针;(该指针)必须指向new出来的数组。
int *p = new int[20];
p[0] = 1;
delete [] p;
-
内联函数
函数调用是有时间开销的,如果函数本身只有几条语句,执行非常快,而且函数被反复执行很多次,相比之下调用函数所产生的这个开销就会显得比较大。为了减小函数调用的开销,引入了内联函数机制,编译器处理对内联函数的调用语句时,是将整个函数的代码插入到调用语句处,而不会产生调用函数的语句。
inline int Max(int a, int b){
if(a > b) return a;
return b;
}
-
函数重载
一个或多个函数,名字相同,然而参数个数或参数类型不相同,这叫做函数的重载。
一下三个函数是重载关系:
int Max(double f1, double f2) { }
int Max(int n1, int n2) { }
int Max(int n1, int n2, int n3) { }
函数重载使得函数命名变得简单。
编译器根据调用语句中的实参的个数和类型判断应该调用哪个函数。
如:
Max(3.4, 2.5);
Max(2, 4);
Max(1, 2, 3);
Max(3, 2.4); //error 二义性
-
函数的缺省参数
C++中,定义函数的时候可以让最右边的连续若干个参数有缺省值,那么调用函数的时候,若相应位置不写参数,参数就是缺省值。
void func(int x1, int x2 = 2, int x3 = 3) { }
fun(10); 等效于func(10, 2, 3)
func(10, 8); 等效于func(10, 8, 3)
func(10, , 8); 不行,只能最最右边的连续若干个参数缺省。
函数参数可缺省的目的在于提高程序的可扩充性。
即如果某个写好的函数要添加新的参数,而原先那些调用该函数的语句,未必需要使用新增的参数,那么为了避免对原先那些函数调用语句的修改,就可以使用缺省参数。
-
类和对象
C语言采用结构化程序设计,即:程序 = 数据结构 + 算法
程序由全局变量以及众多相互调用的函数组成。算法以函数的形式实现,用于对数据结构进行操作。
结构化程序设计的不足:结构化程序设计中,函数和其所操作的数据结构没有直观的联系,随着程序规模的增加,程序逐渐难以理解。
结构化程序设计没有“封装”和“隐藏”的概念,要访问某个数据结构中的某个变量,就可以直接访问,那么当该变量的定义有改动的时候,就要把所有访问该变量的语句找出来修改,十分不利于程序的维护和扩充。
难以查错,当某个数据结构的值不正确时,难以找出到底是哪个函数导致的。
重用:在编写某个程序时,发现其需要的某项功能,在现有的某个程序里已经有了相同或类似的实现,那么自然希望能够将那部分代码抽取出来在新程序中使用。
面向对象的程序设计:能够较好的解决上述问题,
面向对象的程序 = 类+类+类+····+类
设计程序的过程,就是设计类的过程。
-
面向对象的程序设计方法
1、将某类客观事物共同特点(属性)归纳出来,形成一个数据结构(可以用多个变量描述事物的属性)
2、将这类事物所能进行的行为也归纳出来,形成一个个函数,这些函数可以用来操作数据结构(即抽象)
然后,通过某种语法形式,将数据结构和操作该数据结构的函数“捆绑”在一起,形成一个“类”,从而使得数据结构和操作该数据结构的算法呈现出显而易见的紧密关系,这就是“封装”。
其特点是:抽象、封装、继承、多态。
例如:
//从客观事物抽象出类
class CRectangle
{
public:
int w, h;
int Area() {
return w*h;
}
int Perimeter(){
return 2*(w+h);
}
void Init(int w_, int h_){
w = w_;
h = h_;
}
}; //必须有分号
int main(){
int w, h;
CRectangle r; //r是一个对象
cin >> w >>h;
r.Init(w, h);
cout << r.Area() << endl <<
r.Perimeter();
return 0;
}
通过类,可以定义变量。类定义出来的变量,也称作类的实例,就是我们所说的“对象”。
C++中,类的名字就是用户自定义的类型的名字,可以像使用基本类型那样来使用它,CRectabgle就是一种用户自定义的类型。
-
对象的内存分配
和结构变量一样,对象所占用的内存空间的大小,等于所有的成员变量的大小之和。
每个对象各有自己的存储空间,一个对象的某个成员变量被改变了,不会影响到另一个对象。
-
对象间的运算
和结构变量一样,对象之间可以用“=”进行赋值,但是不能用“==”“!=”“>”“<”等进行比较,除非这些运算符经过了重载。
-
使用类的成员变量和成员函数
用法一:对象名.成员名
CRectangle r1, r2;
r1.w = 5;
r2.Init(5, 4);
Init函数作用在r2上,即init函数执行期间访问的w和h是属于r2这个对象的,执行r2.Init不会影响到r1。
用法二:指针—>成员名
CRectangle r1, r2;
CRectangle *p1 = &r1;
CRectangle *p2 = &r2;
p1->w = 5;
p2->Init(5, 4); //Init作用在p2指向的对象上
用法三:引用名.成员名
CRectangle r2;
CRectangle &rr = r2;
rr.w = 5;
rr.Init(5, 4); //rr的值变了,r2的值也变了
void PrintRectangle(CRectangle & r)
{
cout << r.Area() << "," << r.Perimeter();
}
CRectangle r3;
r3.Init(5, 4);
PrintRectangle(r3);