C++学习第一周

314 阅读5分钟

引用的概念

下面的写法定义了一个引用,并将其初始化为引用某个变量。

类型名 & 引用名 = 某变量名;

int n = 4;
int & r = n;//r引用了n,r的类型是int &
某个变量的应用,等价于这个变量,相当于改变量的一个别名

int n = 7;
int & r = n;
r = 4;
cout<<r;//输出4
cout<<n;//输出4
n = 5;
cout<<r;//输出5

	double a = 4,b = 5;
	double & r1 = a;
	double & r2 = r1;//r2也引用a
	r2 = 10;
	cout<<a<<endl;//输出10
	r1 = b;//r1并没有引用b 
	cout << a <<endl;//输出5 

引用的概念

定义引用时一定要将其初始化成引用某个变量

初始化后,他就一直引用该变量,不会再引用别的变量了

引用只能引用变量,不能引用常量和表达式

C语言中,如何编写交换两个整形变量值的函数?

void swap(int a, int b)
{
    int tmp;
    tmp = a;
    a = b;
    b = tmp;
}
int n1,n2;
swap(n1,n2);//n1,n2的值不会被交换

a,b为形参,n1,n2为实参,在函数里面,形参的值修改了,不会影响实参的值
可以使用以下方法实现

void swap(int *a,int *b)
{
    int tmp;
    tmp = *a;
    *a = *b;
    *b = tmp;
}

int n1,n2;
swap(&n1,&n2);//n1,n2的值被交换

也可以使用引用方实现

void swap(int &a,int &b)
{
    int tmp;
    tmp = a;
    a = b;
    b = tmp;
}

int n1,n2;
swap(n1,n2);//n1,n2的值被交换

引用可以作为函数的返回值
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 &

不能通过常引用去修改其引用的内容:
int n = 100;
const int & r = n;
r = 200;//编译错
n = 300;//没问题

常引用和非常引用的转换

Const T &和T &是不同的类型!!!

T &类型的应用或T类型的变量可以用来初始化Const T&类型的应用

Const T类型的常变量和Const T&类型的引用则不能用来初始化T &类型的引用,除非进行强制类型转换

定义常量

const int MAX_VAL = 23;

const double Pi = 3.14;

const Char * SCHOOL_NAME = "Peking University"

定义常量指针

不可通过常量指针修改其指向的内容
int n,m;
const int * p = &n;
*p = 5;//编译出差
n = 4;//ok
p = &m;//ok,常量指针的指向可以变化

输入输出控制符

在printf和scanf中可以使用以“%”开头的控制符,指明要输入或者要输出的数据的类型以及格式。

常用格式控制符以及作用如下

  • %d,读入或者输出int变量

  • %c,读入或者输出char变量

  • %f,读入或者输出float变量,输出时保留小数点后后面6位

  • %lf,读入或者输出double变量,输出时保留小数点后后面6位

  • %x,以十六进制读入或者输出整形变量

  • %lld,读入或者输出long long变量(64位整数)

  • %nd(如%4d,%12d),以n字符宽度输出整数,宽度不足时用空格填充

  • %0nd(如%04d,%012d),以n字符宽度输出整数,宽度不足时用0填充

  • %.nf(如%.4f,%3f),输出浮点数,精确到小数点后n位

面向对象的程序设计

Const关键字

定义常量指针

不能把常量指针赋值给非常量指针,反过来可以

	const int *p1;
	int *p2;
	p1 = p2;//ok
//	p2 = p1;//error
	p2 = (int*)p1;//ok,强制类型转换

函数参数为常量指针时,可以避免函数内部不小心改变参数指针所指地方的内容

void MyPrintf(const char * p)
{
	strcpy(p,"this");//编译出错
	printf("%s",p);//ok
}

此处,strcpy第一个参数的类型实际上是char*,而实际为const char *,并且不能将常量指针赋值给非常量指针。类型不匹配,所以编译出错。

动态内存分配

用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:要分配的数组元素的个数,可以是整形表达式

动态分配出一片大小为N * sizeof(T)字节的内存空间,并且将该内存空间的起始地址赋值

动态分配数组示例:

	int * pn;
	int i = 5;
	pn = new int[i*20];
	pn[0] = 20;
	pn[100] = 30;//编译没问题,运行时导致数组越界

new 运算符返回值类型:

new T;
new T[n];

这2个返回值表达式都是T *

int * p = new int;

用delete运算符释放动态分配的内存

用“new”动态分配的内存空间,一定要用“delete”运算符进行释放

delete 指针;//该指针必须指向new出来的空间

	int * p = new int;
	* p = 5;
	delete p; //释放操作只能做一次
//	delete p;//导致异常,一片空间不能被delete多次

用delete运算符释放动态分配的数组

用“delete”释放动态分配的数组,要加“[]”

delete [] 指针;//该指针必须指向new出来的数组

	int * p = new int[20];
	p[0] = 1;
	delete [] p;
动态分配的存储空间没有被释放的话,就会成为内存的垃圾碎片,
这部分内存在运行期间就不能被其他程序占用了,等于浪费了

内联函数

函数调用是有时间开销的。如果函数本省只有几条语句,执行非常快,而且函数本身被反复执行很多次,相比之下调用函数所产生的这个开销就会显得比较大。

函数调用是又额外开销的,因为我们在调用函数的时候,首先要把参数放到栈里面去,返回地址也要放到栈里面去,函数执行完返回以后,还要从栈里面取出返回地址,再跳转到返回地址里去执行,这些都需要时间,需要几条指令

为了减少函数调用的开销,引入了内联函数机制。编译器处理对内联函数的调用语句时,是将整个函数的代码插入到调用语句出,而不会产生调用函数的语句。

在函数定义前面加“inline”关键字,即可定义内联函数

inline int Max(int a,int b)
{
	if(a>b)
		return a;
	return b;
}
带来的坏处,可执行程序的体积增大

函数重载

一个或者多个函数,名字相同,然而参数个数或者参数类型不相同,这叫做函数的重载。

以下三个函数是重载关系:

int Max(double f1,double f2)
{
	//return 0;
}

int Max(int n1,int n2)
{
	//return 0;
}

int Max(int n1,int n2,int n3)
{
	//return 0;
}

函数重载使函数命名变得简单。

编译器根据调用语句中的实参的个数和类型判断应该调用哪个函数。

int Max(double f1,double f2)
{
	return 0;
}

int Max(int n1,int n2)
{
	return 0;
}

int Max(int n1,int n2,int n3)
{
	return 0;
}

int main()
{
	Max(3.4,2.5);//调用(1)
	Max(2,4);//调用(2)
	Max(1,2,3);//调用(3)
	//Max(3,2.4);//error,二义性
}

函数的缺省参数

C++中,定义函数的时候可以让最右边的连续若干个参数有缺省值,那么调用函数的时候,若相应位置不写参数,参数就是缺省值。

void func(int x1,int x2 = 2,int x3 = 3)
{

}

int main()
{
	func(10);//等效于func(10,2,3)
	func(10,8);//等效于func(10,8,3)
	//func(10,,8);//不行,只能最右边的连续若干个参数缺省
}
  • 函数参数可缺省的目的在于提高程序的可扩充性。
  • 即如果某个写好的函数要添加新的参数,而原先那些调用该函数的语句,未必需要使用新增的函数,那么为了避免对原先那些函数调用语句的修改,就可以使用缺省参数。

结构化程序设计

C语言使用结构化程序设计:

程序 = 数据结构+算法

程序由全局变量以及众多相互调用的函数组成。

算法以函数的形式实现,用于对数据结构进行操作。

结构化程序设计的不足

  • 结构化程序设计中,函数和其所操作的数据结构,没有直观的联系

  • 随着程序规模的增加,程序逐渐难以理解,很难一下子看出来:

  • 某个数据结构到底有哪些函数可以对它进行操作?

  • 某个函数到底是用来操作哪些数据结构的?

  • 任何两个函数之间存在怎样的调用关系?

  • 结构化

面向对象的程序设计能够较好的解决结构化程序设计的不足。

面向对象的程序设计 = 类+ 类+ .......+类

程序设计的过程就是设计一大堆类的过程

面向对象的程序设计

面向对象的程序设计方法:

  • 将某类客观事物的共同点(属性)归纳出来,形成一个数据结构(可以用多个变量描述失误的属性):
  • 将这类事物所能进行的行为也归纳出来,形成一个函数,这些函数可以用来操作数据结构(这一步叫“抽象”)。

然后,通过某种语法形式,将数据结构和操作该数据结构的函数“捆绑”在一起,形成一个“类”,从而使得数据结构和操作该数据结构的算法呈现出显而易见的紧密关系,这就是“封装”。

面向对象的程序设计具有“抽象”,“封装”,“继承”,“多态”四个基本特点。

从客观事物抽象出类

写一个程序,输入矩形的长和宽,输出面积和周长。

比如对于“矩形”这种东西,要用一个类表示,该如何做“抽象”呢?

矩形的属性就是长和宽。因此需要两个变量,分别代表长和宽。

一个矩形,可以有哪些行为呢(或可以对矩形进行哪些操作)?

矩形可以设置长和宽,算面积,和算周长这三种行为(当然也可以有其他行为)。

将长、宽变量和设置长、宽,求面积,以及求周长的三个函数“封装”在一起,就能形成一个“矩形类”。

长、宽变量成为该“矩形类”的“成员变量”,三个函数成为该类的“成员函数”,成员变量和成员函数统称为类的成员。

实际上,“类”看上去就像“带函数的结构”。

从客观事物抽象出类

class CRectangle
{
	public:
		int w,h;
		int Area()
		{
			return w*h;
		}
		int Perimeter()
		{
			return 2*(w+h);
		}
		int Init(int w_,int h_)
		{
			w = w_;
			h = h_;
			return w,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++中,类的名字就是用户自己自定义的类型的名字。可以像使用基本类型那样来使用它。CRectangle就是一种用户自定义的类型。

对象的内存分配

和结构变量一样,对象所占用的内存空间的大小,等于所有变量成员的大小之和。

对象仅包含成员变量,不包含成员函数

对于上面的CRectangle类。sizeof(CRectangle) = 8;

每个对象各有自己的存储空间。一个对象的某个成员变量被改变了,不会影响到另一个对象。

对象间的运算

和结构变量一样,对象之间可以用“=”进行赋值,但是不能用“==”,“!=”,“>”,“<”,“>=”,“<=”进行比较,除非这些运算符进过了“重载”。

使用类的成员变量和成员函数

用法一:对象名.成员名

	CRectangle r1,r2;//r1,r2分别是一个对象
	r1.w = 5;
	r2.Init(5,4);

Init函数作用在r2上,即Init函数执行期间访问的w和h是属于r2这个对象的,执行r2.Init不会影响到r1。

用法二: 指针->成员名

	CRectangle r1,r2;
	CRectangle * p1 = &r1;//p1指向r1
	CRectangle * p2 = &r2;//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);

类的成员函数和类的定义分开写

class CRectangle
{
	public:
		int w,h;

		int CRectangle::Area()//Area为Crectangle的成员函数
		{
			return w*h;
		}

		int CRectangle::Perimeter()
		{
			return 2*(w+h);
		}

		void CRectangle::Init(int w_,int h_)
		{
			w = w_;
			h = h_;
		}
};

CRectangle::说明后面的函数是CRectangle类的成员函数,而非普通函数。那么,一定要通过对象或对象的指针或对象的引用才能调用。

类成员的可访问范围

在类的定义中,用下列访问范围关键字来说明类成员可被访问的范围

-private:私有成员,只能在成员函数内访问

-public:公有成员,可以在任何地方访问

-protected:保护成员,以后再说

以上三种关键字出现的次数和先后次序都没限制。关键字说明类成员的可访问范围。

定义一个类

class className
{
    private:
        私有属性和函数
   public:
        公有属性和函数
  protected:
       保护属性和函数
};

如果某个成员前面没有上述关键字,则缺省地被认为是私有成员。

class Man
{
	int nAge;//私有成员
	char szName[20];//私有成员

	public:
		void SetName(char * szName)
		{
		strcpy(Man::szName,szName);
		}
};

在类的成员函数内部,能够访问

-当前对象的全部属性、函数:

-同类其它对象的全部属性、函数。

在类成员函数以外的地方,只能够访问类对象的公有成员。

class CEmployee
{
	private:
		char szName[30];//名字
	public:
		int salary;//工资
		void setName(char * name);
		void getName(char * name);
		void averageSalary(CEmployee e1,CEmployee e2);
};

void CEmployee::setName(char * name)
{
	strcpy(szName,name);//ok

}

void CEmployee::getName(char * name)
{
	strcpy(name,szName);//ok

}

#include<iostream>
using namespace std;

class CEmployee
{
	private:
		char szName[30];//名字
	public:
		int salary;//工资
		void setName(char * name);
		void getName(char * name);
		void averageSalary(CEmployee e1,CEmployee e2);
};

void CEmployee::averageSalary(CEmployee e1,CEmployee e2)
{
	cout << e1.szName;//ok,访问同类其他对象私有成员
	salary = (e1.salary+e2.salary)/2;

}

int main()
{
	CEmployee e;
	//strcpy(e.szName,"Tom1234567889");//编译错,不能访问私有成员
	//e.setName("Tom");//ok
	e.salary = 5000;//ok
	return 0;

}

注意此处的main并不是CEmployee的成员函数,main中的语句自然也不在CEmployee的成员函数里面,一个类的私有成员不可以在这个类的成员函数外部进行访问。

设置私有成员的机制,叫“隐藏”

“隐藏”的目的是强制对成员变量的访问一定要通过成员函数进行,那么以后成员变量的类型等属性修改后,只需要更改成员函数即可。否则,所有直接访问成员变量的语句都需要修改。

隐藏的作用

如果将上面的程序移植到内存空间紧张的手持设备上,希望将szName改为char szName[5],若szName不是私有,那么就要找出所有类似strcpy(e.szName,"Tom1234567889");这样的语句进行修改,以防数组越界。这样做很麻烦。

如果将szName变为私有,那么程序中就不可能出现(除非在类的内部)

strcpy(e.szName,"Tom1234567889");这样的语句,所有对szName的访问都是通过成员函数来进行,比如:e.setName("Tom1234567889");

那么,就算szName该短了,上面的语句也不需要找出来修改,只要改szName成员函数,在里面确保不越界就可以了。

成员函数的重载及参数缺省

成员函数也可以重载

成员函数也可以带缺省参数

#include<iostream>
using namespace std;

class Location
{
	private:
		int x,y;
	public:
		
		void init(int x = 0,int y = 0);
		void valueX(int val)
		{
			x = val;
		}
		int  valueX()
		{
			return x;
		}
};

void Location::init(int X,int Y)
{
	x = X;
	y = Y;
}

int main()
{
	Location  A,B;
	A.init(5);
	A.init(5);
	cout << A.valueX();
	return 0;

}
//输出5

使用缺省参数要注意避免有函数重载时的二义性

#include<iostream>
using namespace std;

class Location
{
	private:
		int x,y;
	public:
		
		void init(int x = 0,int y = 0);
		void valueX(int val = 0)
		{
			x = val;
		}
		int  valueX()
		{
			return x;
		}
};

int main()
{
	Location  A;
	//A.valueX();//错误,编译器无法判断调用哪个valueX

	return 0;

}