C++对C的扩充

C++既可用于面向过程的程序设计,也可用于面向对象的程序设计。在面向过程程序设计的领域,C++继承了C语言提供的绝大部分功能和语法规定,并在此基础上做了不少扩充,主要有一下几个方面:

1.C++的输入输出

C++为了方便用户,除了可以利用scanf和printf函数进行输入输出外,还增加了标准输入输出流cout和cin。它们在头文件iostream中定义的。

1.1用cout进行输出

cout必须和输出运算符“<<”一起使用。下面给出一个简单的cout输出语句。

#include <iostream>
using namespace std;

int main()
{
	int a = 10;
	cout << a << endl;
	//printf("%d\n",a);
	return 0;
}

cout输出语句与下面的printf函数语句时等价的,endl就相当于'\n'。

利用cout输出数据的时候每输出一项就得用一个“<<”。不能写成“cout<<a,b,c<<endl;”。

使用cout进行输出的时候不用进行格式化输出,即不用"%d"这种的格式化输出,cout会自动查询待输出的数据的类型。所以利用cout输出数据比printf简单。

如果要在屏幕上输出一句话,或者提示信息,要在<<中加上双引号。

如果要指定输出所占的列数,可以用控制符setw进行设置,入setw(5)的作用时为其后面一个输出项预留5列的空间,如果输入出数据项长度不足5列,则数据向右对其,若超过5列则按实际长度输出。说明使用setw,应在程序的开头包含头文件<iomanip>或者<iomanip.h>。 

数据的输出默认是右对齐,如果向修改为左对齐就得使用'left': 

使用left可以使下一个待输出的数据以左对齐的方式输出。 使用left也要包含头文件<iomanip>或者<iomanip.h>。

1.2用cin进行输入

从输入设备向内存流动的数据流称为输入流。cin可以实现从标准设备的输入操作,它的功能与scanf函数相同。使用cin必须得和”>>“运算符一起使用。该运算符从输入设备获取数据并送到输入流cin中,然后再送到内存中。”>>“常称为”提取运算符“。现在给出简单的输入例子:

int main()
{
	int a = 0;
	cin >> a;//scanf("%d",&a);
	cout << a << endl;
	return 0;
}

我们可以看到cin与scanf相比,书写更为简单。与cout相同,cin在输入数据时不用指定数据的类型,系统会自动识别该变量的类型。一次输入多个数据时,不要写在一块,要分开写,输入一个后,以空格作为分割,在输入下一个:

2.用const定义常变量 

在C语言中经常利用#define来定义符号常量,如:#define max 1000

实际上,这种方式只是在预编译时进行字符替换,即将所有的max都替换为100.但是这种方式定义的常量没有类型检查,而且很容易发生意想不到的错误。

而在C++中提出了用const来定义常量。

const int a = 10;

上面就是利用const定义了一个常变量。它实际上还是一个变量,有数据类型,占用储存单元,有地址,可以用指针指向它,只是在程序运行期间该变量的值时固定的,不能被修改。

我们看,我们已经定义a为常变量,a的值就不能在被修改,此时a = 12,就是非法的,会使程序报错。 

3.函数原型声明

我们在调用函数的时候,如果被调函数定义在被调到位置之后,C++强制要对该函数进行声明。这样做的目的是使编译系统对函数调用的合法性进行检查,尽量保证程序的正确性。

上面的max函数的调用就在定义之前,所以我们在使用之前要对其进行声明。 函数声明的一般形式为:

函数类型 函数名 (参数表);参数表既可以包含类型和参数名,也可以只包含参数类型。如:下面两种写法等价:

int max (int x,int y); int max(int ,int)。

但是每次都对函数进行声明之前很麻烦,所以我们在写函数定义的时候,就直接将定义写在调用该函数之前,这样就不必在进行函数声明了。

4.函数的重载 

在前面的程序中用到了插入运算符"<<"和提取运算符">>"。这两个运算符原本是C和C++位运算中的左移运算符和右移运算符,现在C++又把它作为输入输出运算符。允许一个运算符可以用于不同场合,不同的场合有不同的含义,这就叫做运算符的重载,即重新赋予它新的含义。其实就是”一物多用“。

在C++中函数也可以重载。我们在C语言编程的时候,我们会发现有几个不同名的函数进行的时同一类的操作。例如:找出两个整数的最大值,找出两个浮点数的最大值,找出两个字符的最大值。

我们在用C语言实现该方法时,要写三个不同的函数:

int max1(int x, int y);
float max2(float x, float y);
char max3(char x, char y);

但是我们知道这三个函数内部的代码是相同的,不同的只是函数的参数和返回值。而C++提出了函数重载的概念就可以很方便的解决该问题。

我们可以将这三个函数的函数名统一为max,然后我们调用max函数时到底调用的是哪一个max是由你传的参数决定的。如果你传的是整型,则调用整型max函数,如果你传的是char,则调用字符max函数。

C++允许在同一作用域中用同一函数名定义多个函数,这些函数的参数个数和参数类型不同,这些同名函数用来实现不同的功能。这就是函数的重载,即一个函数名多用。 

注意:重载函数的参数个数或参数类型至少有一个不同,即要不参数个数相同而参数类型不同,要不参数类型相同参数个数不同,要不两者都不同。函数的返回值可以相同也可以不同。 不允许参数个数和参数类型都相同而只有返回值不同,因为系统无法从函数的调用形式上判断那一个函数与之匹配。

下面给出一个函数重载的例子:利用函数重载实现求两个整数以及三个整数的最大值。

#include <iostream>
#include <iomanip>
using namespace std;

int max(int x, int y)
{
	return x > y ? x : y;
}

int max(int x, int y, int z)
{
	if (x < y)
	{
		x = y;
	}
	if (x < z)
	{
		x = z;
	}
	return z;
}

int main()
{
	int a = 1;
	int b = 2;
	int c = 3;
	cout << max(a, b) << endl;
	cout << max(a, b, c) << endl;
	return 0;
}

上面的函数重载就体现了参数个数不同而参数类型和返回值类型相同。系统会根据你传递参数的个数,来判断该调用那一个函数。

5.函数模板 

我们刚才介绍了函数重载可以实现一个函数名多用,将实现相同或类型功能的函数用同一个函数名来定义。这样是编程者在调用同类函数是感觉到含义清楚,方法简单。但是在程序中仍然要分别定义每一个函数,就像刚才说过的求两个整型,浮点型,字符型的数据的最大值,它们的函数体是完全相同的,区别只在于返回值和参数类型的不同。

于是C++有提出了一个新的概念——函数模板。   

所谓函数模板,实际上就是建立一个通用的函数,其函数的返回值和参数类型是没有指定的,用一个虚拟的类型代表,这个通用的函数就叫做函数模板。凡是函数体相同的函数都可以用该函数模板来代替,不必再定义多个函数,只须再模板中定义一次即可。再调用该模板函数的时候,系统会根据实参的类型,将虚拟类型替换为实参的类型,实现了不同函数的功能。

下面将求两个数的最大值用函数模板实现:

#include <iostream>
#include <iomanip>
using namespace std;

template<typename T>
T tmax(T x, T y)
{
	return x > y ? x : y;
}

int main()
{
	int a = 1;
	int b = 2;
	float c = 1.2f;
	float d = 3.2f;
	char e = 'a';
	char f = 'x';
	cout << tmax(a, b) << endl;
	cout << tmax(c,d) << endl;
	cout << tmax(e,f) << endl;
	return 0;
}

下面就是定义的函数模板,T就是虚拟类型,他会在函数调用的时候由实参的类型来确定。 

template<typename T>
T tmax(T x, T y)
{
	return x > y ? x : y;
}

当我们传给该函数两个整型时,相当于函数变成了:

int max(int x, int y)
{
	return x > y ? x : y;
}

定义函数模板的一般形式为:

template<typename T>

函数体

或者

template<class T>

函数体

template的含义是”模板“,尖括号中先写关键字typename(或class)后面跟一个类型参数T,这个类型参数实际上是一个虚拟的类型名,表示模板中出现的T都只是一个类型而已,但是现在并没有指定他具体是哪一种类型,等到函数调用的时候,由实参的类型来决定。T只是一个标识符而已,也可以采用其他的。

class与typename的作用相同,都是表示”类型名“,而这可以互换。但因为class容易与C++中的‘类’搞混,所以建议使用typename来表示类型名。

类型参数可以不止一个,根据需要确定个数:

template<typename T1,typename T2>

 可以看到,用函数模板比函数重载更方便,程序更简洁。但应注意它只适用于函数的参数个数相同而类型不同,且函数体相同的情况,如果参数个数不相同,则不能用函数模板。

6.有默认参数的函数 

一般情况下,在函数调用时形参从实参那里得到值,因此实参的个数应与形参相同。而在有时多次调用同一函数时使用了同样的实参,C++提供了简单的处理办法,给形参一个默认值,这样形参就不必一定要从实参那里取值了。如:

float V(float r = 6.53);

即,指定r的默认值为6.53。如果调用该函数时,想使用默认值,则在调用该函数时,不必传参数。如:

float V(float r = 6.53);

int main()
{
	V();//r = 6.53
	return 0;
}

此时,r的值就为默认值6.53.如果不想使用默认值,而需要自己给值的话,只需将需要的值传给该函数即可。如:

float V(float r = 6.53);

int main()
{
	V(1.2);//r = 1.2
	return 0;
}

如果有多个形参,可以使每一个形参都有一个默认值,也可以只使一部分由默认值。例如由一个求圆柱体的体积的函数,形参h为高,r为底面半径。函数原型如下:

float V(float h,float r = 6.53);

该函数即默认该圆柱体的底面半径为6.53。该函数的调用有以下两种方式:

V(32.1);//h为32.1,r为默认值6.53

V(32.1, 12.1);//h为32.1,r为12.1

实参与形参的结合是从左向右按顺序进行的,第一个实参必然与第一个形参结合,第二个实参必然与第二个形参结合……因此指定默认值的参数必须放在形参表列的最右端,否则会出错。

void f1(int x, int y, int z = 0, int n);//错误
void f2(int x,int y,int n,int z = 0)//正确

那么为什么会出错呢?如果我们想让x为1,y为2,z就为默认值0,n为3,那么我们要怎么传参呢?

f1(1, 2, 3);我们想这样调用,但是其实这时候z的值不是默认值而是3了,n没有值。所以当我们将带有默认值的形参没有放在形参表列的最右端时,就会在传参时造成歧义。

在使用带有默认参数的函数时有两点要注意:

  • 必须在函数调用之前将默认值的信息通知给编译系统。如果在声明函数时已对形参给出了默认值,而在定义函数是又对形参给出默认值,有的编译系统会给出”重复指定默认值“的报错信息,有的编译器对此不报错,甚至允许在声明时和定义时的默认值不同,此时由编译器先遇到谁为准。为了避免混淆,最好只在函数声明时指定默认值。
  • 一个函数不能既作为重载函数,有作为有默认参数的函数。因为在当函数调用时,如果少写了一个参数,系统不知道使利用重载函数还是利用默认参数的函数,会出现歧义。

例如:int max(int a,int b,int c = 100);

int max(int a,int b);

如果调用时为max(12,22);,编译系统无法知道是调用两个参数的max函数,还是调用带有默认值的函数。

7.变量的引用

变量的引用时C++对c的一个重要补充。

7.1引用的概念

在C++中,变量的“引用”其实就是变量的别名,因此引用又称别名。建立引用的作用是为变量再取一个新的名字,以便在需要时可以方便、间接的引用该变量。当你给一个变量起了一个别名之后,就相当与这个变量有个两个名字,你不管引用哪一个名字都可以对该变量进行修改。例如,有一个变量a,你要给它起一个别名:

int a = 10;
int& b = a;//声明b是整型变量a的引用

经过这样的声明之后,使用a和b的作用是相同的,都代表同一个变量。对任何一个修改,另一个也会发生改变。

我们给a起了一个别名b之后,将b修改了,a也会修改,因为两者对应着同一块内存空间。 

注意:

上述声明中,'&'是引用声明符,而不是取地址操作符。

对变量声明一个引用,并不开辟内存单元,b和a代表的同一块内存单元。

我们调试发现两者确实代表着相同的内存单元。

在声明一个引用的时候,必须同时是指初始化,即声明它代表哪一个变量。 

一个引用不能作为两个变量的别名。

在上图中,c既作为a的别名又作为b的别名,这是不允许的。一个引用只能作为一个变量的别名。

7.2引用的简单使用 

我们声明了b是变量a的引用,我们对a进行修改,a和b会同步发生变化,对b进行修改,a和b也会同时发生变化。 

7.3关于引用的简单说明

  • 引用并不是一种独立的数据类型,它必须与某一种类型的数据相联系。声明引用时必须指定它代表的是哪一个变量,即对它进行初始化。
int a = 10;
int& b = a;//正确,声明b是a的引用,即b是变量a的一个别名
int& b;//错误,没有指定b代表那个变量

float c = 1.1;
int& d = c;//错误,声明b是一个整形变量的别名,但是却让它代表了浮点型变量c
  • 引用与其所代表的变量共享同一块内存单元,系统并不为引用另外分配储存空间。实际上,编译系统是引用和其代表的变量具有相同的地址。

我们打印他们的地址,的确是相同的。注意,cout里面的'&'是取地址操作符,而不是引用声明符。

  • 如果该&a前面有类型名,即(int & a),则必然是对引用的声明;如果前面没有类型名即(p = &a),此时的'&'是取地址运算符。
  • 对引用的初始化,可以用变量名,也可以用另外一个引用。 
int a = 10;
int& b = a;
int& c = b;

b是a的别名,c是b的别名,那c也就是a的别名。

  • 引用在初始化为某一个变量的别名之后,不能再被重新声明为另一个变量的别名。 

7.4将引用作为函数参数

 将引用作为函数参数的最用类似于指针的作用。假如我们要写一个函数用来交换两个变量,C语言中我们通过传该变量的地址来修改。现在我们可以通过引用来实现这个功能,不必再传地址。

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

int main()
{
	int a = 10;
	int b = 99;
	cout << "a = " << a << " " << "b = " << b << endl;
	swap(a, b);
	cout << "a = " << a << " " << "b = " << b << endl;
	return 0;
}

我们看到,我们利用变量的引用也可以是实现两个变量的交换。我们设置函数的时候形参表设为某个变量的引用。但是此时并没有进行初始化,当我们将实参ab传过去的时候,swap中的a就成了实参a的别名,b成了实参b的别名。而我们前面知道了别名和变量指的是同一块内存,所以我们将别名交换了,实参本身也就交换了。

实际上,实参传给形参的是实参的地址,也就是使形参a和实参a具有同样的地址,从而是两者共用同一块内存单元。 

7.4.1使用引用和使用指针变量作为函数参数有什么不同?

  • 不必再swap函数中设立指针变量,指针变量要另外开辟内存单元,其内容是地址。而引用不是一个独立的变量,不单独站内存单元。
  • 在main函数中调用swap函数值,实参不必在变量名前加'&'以表示地址。系统传送的是实参的地址,二部制实参的值。
  • 在使用指针变量的时候要对其进行解引用操作。而使用引用时,引用就代表了该变量,不必解引用。
  • 用引用能完成的工作,用指针也能完成。但用引用比用指针直观,方便,直截了当。

7.5对引用的进一步说明 

7.5.1不能建立void类型的引用。

任何实际存在的变量都是有具体类型的。而我们在声明引用的时候就要完成初始化,所以此时已经知道了引用的类型。

7.5.2不能建立引用的数组

char a[4] = "abcd";
char& r[4] = c;//错误

数组名c只代表首元素的地址,本身并不是一个占有存储空间的变量。

7.5.3可以将变量的引用的地址赋给一个指针变量,此时指针指向的是原来的变量

int a = 3;
int& b = a;
int* p = &b;

此时a.b.*p指向的是同一块内存,都代表了a变量。

但是不能定义指向引用类型的指针变量,不能写成

int&* p = &a;

由于引用不是一种独立的数据类型,因此不能建立指向引用类型的指针变量。

7.5.4可以建立指向指针类型的引用

int i = 1;
int* p = &i;
int*& pr = p;

pr先与'&'结合,说明pr是一个引用,指向的是一个int* 类型的变量。所以此时引用pr也相当于一个指针变量了,指向的是i。此时*pr的值,就相当于*p的值,就是i的值。

7.5.5可以用const对引用加以限定,不允许改变该引用的值。

int main()
{
	int a = 14;
	const int& b = a;//用const修饰的引用不能被修改
	b = 1;//错误
	a = 1;//但是可以直接改变变量本身
	return 0;
}

此时输出a和b的值,都是1。

7.5.6可以用常量或表达式对引用进行初始化,但此时必须用const声明。

如:

int i = 5;
const int& a = i + 3;

8.内置函数

调用函数时需要一定的时间,如果有些函数需要频繁的使用,则累计所用时间会很长,从而降低程序的执行效率。C++提供一种提高效率的方法,即在编译时将所调用的函数的代码嵌入到主调函数中。这种嵌入主调函数中的函数称为内置函数,又称内嵌函数或者内联函数

指定内置函数只需在函数首行的左端加上关键字inline即可。

例:将比较两个数的大小写成内置函数。

inline int max(int x, int y)
{
	return x > y ? x : y;
}

int main()
{
	int a = 1;
	int b = 2;
	int c = max(a, b);
	cout << c << endl;
	return 0;
}

将max作为内联函数后,在编译的时候,系统会将主函数中的max展开如下:

int main()
{
	int a = 1;
	int b = 2;
	int c = 0;
	x = a;
	y = b;
	c = x > y ? x : y;
	c = max(a, b);
	cout << c << endl;
	return 0;
}

使用内置函数可以节省运行时间,但却增加了目标程序的长度。假设要调用10次max函数,则在编译时先后10次将max的代码复制并插入main函数中,大大增加了main函数的长度。因此只有对规模很小且使用频繁的函数,才可大大提高运行速度。

9.作用域运算符

每一个变量都有其有效的作用域,只能在变量的作用域内使用该变量,不能直接使用其他作用域1中的变量。

假如有一个全局变量a为浮点型,main函数中也有一个a为整型,当我们在主函数中打印a时,将会打印整型a,而不是浮点型a。因为,在main函数中,局部变量将屏蔽全局变量。

那如果我们就是想打印浮点型的a呢?

C++提供作用域运算符“::”,它能只当所需要的作用域。

float a = 1.1;

int main()
{
	int a = 10;
	cout << a << " " << ::a << endl;
	return 0;
}

"::"表示全局作用域中的变量a。请注意:不能用“::”访问函数中的局部变量。

10.字符串变量 

C++提供了一种新的类型——字符串类型(string),可以用此来定义字符串变量。

实际上,string并不是C++语言本身具有的基本类型(而int char double float等是C++本身提供的基本类型),它是在C++标准库中声明的一个字符串类,用这种类可以定义字符串变量。

10.1定义字符串变量

我们可以按照定义int类型等方式来定义字符串变量。

string s1;
string s2 = "china";

应当注意:定义string类字符串变量要包含头文件<string>。注意不是<string.h>。

10.2对字符串变量进行赋值

如:

string s1 = "china";

既可以用一个字符串常量给字符串变量赋值,也可以用另一个字符串变量对其赋值。

string s1;
string s2 = "china";
s1 = s2;//s1的内容也是"china"

这样赋值不要求s1和s2的长度相同。字符串变量的长度会根据其中的内容而改变。

string s1 = "ab";
string s2 = "china";
s2 = s1;//s2变成了ab

可以对字符串变量中某一个字符进行操作,如:

string word = "she";
word[2] = 'a';//此时word的内容为"sha"

需要注意的是,字符串常量以'\0'结尾,但是当把其赋给字符串变量的时候,字符串变量不会存储'\0'。因此上面的word变量中只有三个字符,而不包括'\0'。

10.3字符串变量的输入输出

可以通过cin和cout对string字符串变量进行输入输出操作。

int main()
{
	string s1;
	string s2;
	cin >> s1 >> s2;
	cout << s1 << s2 << endl;
	return 0;
}

10.4字符串变量的运算

在以字符数组存放字符串时,字符串的运算要通过字符串函数,如strcat(连接)、strcmp(比较)、strcpy(复制),而对string类对象,可以使用更简单的运算符。

10.4.1字符串复制用赋值号

str1 = str2;

10.4.2两字符串连接用+号

string s1 = "hello";
string s2 = "world";
s1 = s1 + s2;//连接s1和s2,s1 = "helloworld"

10.4.3字符串比较直接使用关系运算符

可以直接用 == < > <= >=等关系运算符来比较字符串。

10.5字符串数组

不仅可以用string定义字符串变量,也可以定义字符串数组。

string str[3] = { "zhangsan","lisi","wangwu" };

此时,str字符串数组的内部情况:

我们可以看到:

  • 这个字符串数组包含三个元素,每个元素都是一个字符串变量
  • 字符串数组不要求每个元素长度相同,长度会随着赋给它的值而改变
  • 字符串数组的每一个元素存放一个字符串变量,而不是一个字符
  • 没有一个字符串元素都不包含'\0',只包含自己本身。

其实之所以会出现这种情况是因为字符串数组存放的其实是每一个字符串变量的首地址。

11.动态分配/撤销内存的运算符new和delete

在C语言中我们进行动态内存管理常用到malloc、realloc、calloc以及free这几个函数。

C++提供了较简便而功能性较强的运算符nwe和delete来取代mallco以及free函数。例如:

new int;//开辟一个整形的空间,返回一直指向整形数据的指针
new int(100);//开辟一个整形的空间,并指定该整数的初始值为100
new char[3];//开辟一个存放字符数组的空间,该空间可以存放3个字符,返回一个指向字符数据的指针
new int[5][4];//开辟一个二维数组

new运算符的一般格式为

new 类型[初值];用new分配数组空间时,不可以进行初始化。

delete运算符的一般格式为

delete []指针变量;

例如:撤销整型变量a,delete a;

撤销字符数组b,delete[]b;

注意:new和delete是运算符,不是函数,因此执行效率高。new和delete要配合使用。

完! 

  • 43
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 19
    评论
C是一种通用的高级编程语言,具有以下特点。 首先,C是一种简洁而高效的语言。它的语法相对简单,易于学习和理解。C的设计注重效率,它支持直接访问内存和底层硬件,因此在性能方面表现出色。这使得C成为编写高性能和系统级应用程序的首选语言。 其次,C是一种可移植性强的语言。C的代码可以在不同的计算机平台上编译和运行,因为它是基于标准规范的。这种可移植性使得C广泛应用于跨平台开发和嵌入式系统等领域。 另外,C是一种模块化的语言。它支持函数的定义和调用,可以将程序划分为多个独立的模块,使得代码更具可读性和维护性。这也为软件工程师提供了便利,可以进行团队合作和模块化开发。 在C的发展方面,首先,C语言衍生出了C++语言C++在C的基础上增加了面向对象的特性,使得程序设计更加灵活和可扩展。C++成为了许多大型软件项目和游戏开发的首选语言。 其次,C语言也通过标准库的不断扩充和改进得到了发展。标准库提供了大量的函数和工具,方便开发人员进行常见的操作和任务。随着技术的进步和需求的增加,C语言的标准库也在不断更新和完善,以适应不同领域的需求。 此外,C语言也影响了其他编程语言的发展。许多现代编程语言如Java、Python等都受到了C语言的影响,在语法和特性上有所借鉴。C语言的设计理念和思想也为后来编程语言的发展提供了指导和启示。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值