从c到c++

书中这么写着:
C语言是结构化和模块化的语言,它是面向过程的。在处理较小规模的程序时,程序员用C语言较为得心应手。但是当问题比较复杂、程序的规模比较大时,结构化程序设计方法就显出它的不足。C程序的设计者必须细致地设计程序中的每一个细节,准确地考虑到程序运行时每时刻发 生的事情,例如各个变量的值是如何变化的,什么时候应该进行哪些输人,在屏幕上应该输出什么等。这对程序员的要求是比较高的,如果面对的是一个复杂问题,程序员往往感到力不从心。当初提出结构化程序设计方法的目的是解决软件设计危机,但是这个目标并未完全实现
为了解决软件设计危机,在20世纪80年代提出了面向对象的程序设计(ObjectOriented Programming,OOP)思想,需要设计出能支持面向对象的程序设计方法的新语言。Smalltalk 就是一种面向对象的语言。而在实践中,人们发现由于C语言是如此深人人心,使用如此广泛,面对程序设计方法的革命,最好的办法不是另外发明种新的语 言去代替它,而是在它原有的基础上加以发展。在这种形势下,C++应运而生。C++是由AT&T Bell(贝尔)实验室的Bjarne Strousrup 博士及其同事于20世纪80年代初在C语言的基础上开发成功的。
C++保留了C语言原有的所有优点,并增加了面向对象的机制。由于C++对c的改进主要体现在增加了适用于面向对象程序设计的“类(clss)。因此最初它被Bjarme Sroustrup称为“带类的C"。后来为了强调它是C的增强版,用了C语言中的自加运算符“++”,改称为C++。
C++是由C发展而来的,与C兼容。用C语言编写的程序基本上可以不加修改地用于C++。从C++名字可以看出它是C的超集。C++既可用于面向过程的结构化程序设计,又可用于面向对象的程序设计,是个功能强 大的混合型的程序设计语言。
C++对C的“增强”,表现在两个方面:
1.在原来面向过程的机制基础上,对C语言的功能做了不少扩充
2.增加了面向对象的机制

面向对象程序设计是开对开发较大规模的程序而提出来的,目的是提高软件开发的效率

c++的输入输出

C++为了方便用户,除了可以利用prinf和scanf函数进行输出和输人外,还增加了标准输人输出流coutcin。cout是由c和out两个单词组成的,代表C++的输出流对象,cin是由C和in两个单词组成的,代表C++的输人流对象。它们是在头文件iostream中定义的。键盘和显示器是计算机的标准输人输出设备,所以在键盘和显示器上的输人输出称为标准输人输出,标准流是不需要打开和关闭文件即可直接操作的流式文件

用cout进行输出
cout必须和输出运算符“<<”起使用。“<<在C语言中是作为位运算中的左移运算符,在C++中对它赋以新的含义:作为输出信息时的“插入运算符”。

 cout<<”Hello<<endl;//将字符串“Hello"插入到输出流cout中,也就是说把所指定的信息输出在标准输出设备上。  cout<<a<<b<<c<<endl; 
  1. 可以不用“n”而用endl控制换行,在头文件istrem中定义了控制符endl代表回车换行操作作用与“n”相同。endl 的含义是end of lie,表示结束一行。
  2. 可以在一个输出语句中使用多个运算符“<<将多个输出项插人到输出流cout中,“<<”运算符的结合方向为自左向右因此各输出项按自左向右顺序插入到输出流中。

用cin进行输入
从输入设备向内存流动的数据流称为输入流。用cin实现从系统默认的标准输入设各(键盘)向内存流动的数据流称为标准输人流。用“>>”运算符从输人设备键盘取得数据并送到输人流cin中,然后再送到内存。在C++中,这种输入操作称为“ 提取“ 或“得到”。“>>” 常称为“提取运算符”。

int a;        //定义整型a double b;	//定义浮点型b cin>>a>>b;  //从键盘接收一个整数和实数  cout<<a<<b<<endl;        //输入输出都不需要在指定数据类型了 如整形--%d,浮点数--%f 

缺省函数(有默认参数的函数)
缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。

void TestFunc(int a = 0)   //定义缺省函数 { 	cout<<a<<endl; } int main() { 	TestFunc(); // 没有传参时,使用参数的默认值 0 	TestFunc(10); // 传参时,使用指定的实参 10 } 

缺省参数分类
1.全缺省参数(参数全给上默认值)
2.半缺省参数(部分,不是说一半的意思)

void TestFunc(int a = 10, int b = 20, int c = 30) //全缺省 { cout<<"a = "<<a<<endl; cout<<"b = "<<b<<endl; cout<<"c = "<<c<<endl; } void TestFunc(int a, int b = 10, int c = 20)  //半缺省 { cout<<"a = "<<a<<endl; cout<<"b = "<<b<<endl; cout<<"c = "<<c<<endl; } 
//a.h void TestFunc(int a = 10);//头文件声明 // a.c void TestFunc(int a = 20)   //c文件定义 {} // 注意:如果生命与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。 

注意
1.形参和实参的结合是从左至右顺序的
2. 半缺省参数必须从右往左依次来声明给出,也不能间隔着给。
3. 缺省参数不能在函数声明和定义中同时出现(编译系统会给出”重复指定默认值“的报错信息)
4. 一个函数不能作为重载又作为缺省,调用函数时,无法判定参数的性质会出现二义性。

函数重载

函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 顺序)必须不同,常用来处理实现功能类似数据类型不同的问题.

int Add(int left, int right) { 	return left+right; } double Add(double left, double right) { 	return left+right; } //参数类型不同   short Add(short left, short right) { 	return left+right; } int Add(short left, short right) { 	return left+right; } //参数顺序不同 

为什么C语言不支持函数重载

从函数编译链接阶段来解释这个问题。
名字修饰是一种在编译过程中,将函数、变量的名称重新改编的机制,简单来说就是编译器为了区分各个函数,将函数通过某种算法,重新修饰为一个全局唯一的名称。底层调用时就可以直接call这个名字。

C语言的名字修饰规则非常简单,只是在函数名字前面添加了下划线。
比如,对于以下代码,在最后链接时就会出错:

int Add(int left, int right); int main() { 	Add(1, 2); 	return 0; } 
编译器报错:error LNK2019: 无法解析的外部符号 _Add,该符号在函数 _main 中被引用。 

上述Add函数只给了声明没有给定义,因此在链接时就会报错,提示:在main函数中引用的Add函数找不到函数体。从报错结果中可以看到,C语言只是简单的在函数名前添加下划线
结论:因此C语言中当工程中存在相同函数名的函数时,就会产生冲突,无法支持函数重载

而C++修饰规则比较复杂,不同编译器在底层的实现方式可能都有差异。

int Add(int left, int right); double Add(double left, double right);  //函数重载 int main() { 	Add(1, 2); 	Add(1.0, 2.0); 	return 0; } 

在vs下,对上述代码进行编译链接,最后编译器报错:

error LNK2019: 无法解析的外部符号 "double cdecl Add(double,double)"(Add@@YANNN@Z) error LNK2019: 无法解析的外部符号 "int __cdecl Add(int,int)" (?Add@@YAHHH@Z) 

通过上述错误可以看出,编译器实际在底层使用的不是Add名字,而是被重新修饰过的一个比较复杂的名字,被重新修饰后的名字中包含了:函数的名字以及参数类型。这就是为什么函数重载中几个同名函数要求其参数列表不同的原因。只要参数列表不同,编译器在编译时通过对函数名字进行重新修饰,将参数类型包含在最终的名字中,就可保证名字在底层的全局唯一性。
总结:
1.C语言不支持同名函数,应为链接的时候函数名一样所以区分不开
2.c++支持重载是因为编译后的同名函数修饰名字不同,根据函数名、类名、调用约定、参数类型等共同决定。

内联函数

以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用时压栈(栈帧)的开销,内联函数提升程序运行的效率。

inline int max(int a,int b)   //修饰内联函数 { 	return a>b? a:b; }  int main() { 	cout<<max(1,2)<<endl;  //会在编译调用的时候 把定义块展开嵌入到程序中,而不是开辟栈帧去调用   } 

总结:
1 inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。
2. inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等
等,编译器优化时会忽略掉内联。
3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
4. 内联函数类似于宏函数,但是又有区别:宏函数不能调试,而内联函数可以

引用

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。对一个变量的引用的所有操作,实际上都是对其代表的变量的操作。

基本规则
1 引用在定义时必须初始化
2. 一个变量可以有多个引用
3. 引用一旦引用一个实体,再不能引用其他实体
注意:引用类型必须和引用实体是同种类型的

 void Test() { 	int a = 10; 	int& ra = a;//定义引用类型 	printf("%pn", &a);   	printf("%pn", &ra);  //地址相同,说明共用一块内存 } 
void Test() { 	int a = 10; 	// int& ra; // 未初始化 该条语句编译时会出错 	int& ra = a; 	int& rra = a;  //多个引用 	printf("%p %p %pn", &a, &ra, &rra); } 
void Test() { 	int a = 10; 	int b = 2; 	int& ra = a; 	int& ra = b;//错误,ra已是a的引用,不能再声明为b的引用了 } 

常引用
引用与const联系之后。
规则:
1.本来是const类型的只读变量,不能被普通变量(可读可写)来引用。理解为权限被放大了(越权)
2.可读可写的普通变量,可以被初始化用来做常引用。理解为权限缩小(管辖之内)

void TestConst() { 	const int a = 10; 	//int& ra = a; // 该语句编译时会出错,a为常量 只读 	const int& ra = a;   	 	// int& b = 10; // 该语句编译时会出错,b为常量 	const int& b = 10; 	 	double d = 12.34; 	//int& rd = d; // 该语句编译时会出错,类型不同 	const int& rd = d;  	//上面的解释如下 	//先将double类型的变量转换成int型,存放在临时变量temp中 	//temp和rd同类型的,就可以声明引用了 	int temp=d; 	const int &rd=temp; } 

引用做参数

void Swap(int& left, int& right)   //调用时自动变为实参的引用 { 	int temp = left; 	left = right; 	right = temp; } int main() { 	int a=1; 	int b=2; 	swap(a,b); 	cout<<a<<b<<endl; } 

引用做返回值

int& Add(int a, int b) { 	int c = a + b; 	return c; } int main() { 	int& ret = Add(1, 2); 	Add(3, 4);  //调用这一步,上面那一步的栈帧空间被释放 	cout << "Add(1, 2) is :"<< ret <<endl;   //7 	return 0; } 

c++ 入门基础
注意:如果函数返回时,离开函数作用域后,其栈上空间已经还给系统,因此不能用栈上的空间作为引用类型返回。
如果以引用类型返回,返回值的生命周期必须不受函数的限制(即比函数生命周期长)。

可以用static定义静态变量
c++ 入门基础

传值、传引用效率比较
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,而对于引用来说,并没有产生临时拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。

引用和指针的区别

语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
底层实现上实际是有空间的,因为引用是按照指针方式来实现的。

int main() { 	int a = 10; 	int& ra = a;//引用 	int* pa = &a;//指针 	return 0; } 

c++ 入门基础
反汇编下我们可以看到两者的底层指令是一样的,lea是取变量地址的汇编指令,可见底层实现都是一样的。

引用和指针的不同点:
1 引用在定义时必须初始化,指针没有要求
2. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型
实体
3. 没有NULL引用,但有NULL指针
4. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
5. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
6. 有多级指针,但是没有多级引用
7. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
8. 引用比指针使用起来相对更安全

auto关键字(C++11)

auto简介
在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量
C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。

TestAuto() { 	return 10; } int main() { 	int a = 10; 	auto b = a; 	auto c = 'a'; 	auto d = TestAuto(); 	cout << typeid(b).name() << endl;  //int 	cout << typeid(c).name() << endl;   //char 	cout << typeid(d).name() << endl;   //int 	//auto e; 无法通过编译,使用auto定义变量时必须对其进行初始化 	return 0; } 

auto与指针和引用结合
用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&

int main() { 	int x = 10; 	auto a = &x;    	auto* b = &x;   //两者没区别 	 	auto& c = x;   //引用必须加& 	return 0; } 

注意:
1.auto不能作为函数参数的类型

// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导 void TestAuto(auto a) {} 

2.auto不能用来声明数组

void TestAuto() { 	int a[] = {1,2,3}; 	auto b[] = {456};  //错误 } 

基于范围的for循环(C++11)

对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。

void TestFor() { 	int array[] = { 1, 2, 3, 4, 5 }; 	for(auto& e : array) //要想操作数据 要注意加&引用符  e就是对数组里的元素的引用 		e *= 2;   //每个数乘2的操作  修改 	for(auto e : array)  //遍历数组   e就是对元素的拷贝 		cout << e << " ";   //没修改 	return 0; } 

注意:与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环

int array[] = { 1, 2, 3, 4, 5 }; 		for (auto& e : array) 			e *= 2; 		for (auto e : array) 		{ 			if (e == 8) 				continue; //或者break 			cout << e << " "; //2,4,6,10   或者 2,4,6 		} 

范围for的使用条件
1.for循环迭代的范围必须是确定的
对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin()和end()的方法,begin和end就是for循环迭代的范围。
2.迭代的对象要实现++和==的操作(遍历或判断的条件)。

指针空值nullptr(C++11)

NULL:
NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:

#ifndef NULL #ifdef __cplusplus #define NULL 0             //被定义为 0 #else #define NULL ((void *)0)    //无类型指针(void*) #endif #endif 

可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,比如

void f(int) { 	cout<<"f(int)"<<endl; } void f(int*) { 	cout<<"f(int*)"<<endl; } int main() { 	f(0); 	f(NULL); 	f((int*)NULL);   //必须强转为一个(int*)才可以调用 上面的第二个函数 	return 0; } 

程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。

nullptr
为了考虑兼容性,C++11并没有消除常量0的二义性,C++11给出了全新的nullptr表示空值指针。C++11为什么不在NULL的基础上进行扩展,这是因为NULL以前就是一个宏,而且不同的编译器厂商对于NULL的实现可能不太相同,而且直接扩展NULL,可能会影响以前旧的程序。因此:为了避免混淆,C++11提供了nullptr,即:nullptr代表一个指针空值常量。nullptr是有类型的,其类型为nullptr_t,仅仅可以被隐式转化为指针类型,nullptr_t被定义在头文件中:

typedef decltype(nullptr) nullptr_t; 

注意:
1.在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
c++ 入门基础
3.为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。

new和delete

动态内存管理,在C语言中是利用库函数malloc与free来分配和撤销内存空间的。但是malloc函数必须要先知道需要指定的空间大小。调用格式为malloc(size),size是字节数,也是要先知道或者用sizeof运算符来求出。而且,malloc的返回值为(void*),必须在程序中强制类型转换,才可以知道返回的指针指向的类型。
而c++提供了较简便而功能强大的运算符new和delete来取代malloc和free函数,但是也保留了这两函数。

new int ;//开辟一个存放整数的空间,返回一个指向整数类型的指针 new int(10); //开辟一个存放整数的空间,并指定整数初值为10 new char[10]; //开辟一个存放字符数组的空间,有十个元素,返回一个指向字符数据的指针 new int[5][6]; //开辟一个存放二维数组的空间,数组大小为 5*6,  float *p=new float(1.12);//开辟一个存放实数的空间,初值为1.12,再将返回的指针赋给指针变量p char *pc= new char[100];  delete p;//撤销new开辟的存放实数的空间 delete []pc;  //撤销数组空间  要在前面加一对方扣号   struct Student { 	...// }  Student *p=new Student;//开辟一个结构体类型的空间 delete p; //销毁 

注意:
1.用new分配数组空间时不能指定初值。
2.由于空间内存不足等原因,分配失败时,new会返回一个空指针NULL。
3.new和delete是运算符,不是函数,所以执行效率高,且两者要配合使用(凑一对)。

  • 版权声明:文章来源于网络采集,版权归原创者所有,均已注明来源,如未注明可能来源未知,如有侵权请联系管理员删除。

发表回复

后才能评论