C++学习笔记(一)——C++与C的区别

C++学习笔记(一)——C++与C的区别

在Centos上安装C++编译器

g++的安装 sudo yum install gcc-c++
测试方法,编写一个简单的test.cc文件后在命令行中输入:g++ test.cc,后回车如果有出错信息(说明你的test.cc文件存在错误)或者是test.cc程序运行出的结果则说明g++已经安装上。如果没有安装上,则输入下面两个指令进行安装:sudo apt-get update和sudo yum install gcc-c++。然后再测试以下。

C++与C的区别

C++是一种通用程序设计语言,支持多重编程模式,例如过程化程序设计、数据抽象、面向对象程序设计、泛型程序设计和设计模式等
比雅尼·斯特劳斯特鲁普博士在贝尔实验室工作期间在20世纪80年代发明并实现了C++。起初,这种语言被称作“C with Classes”(“包含‘类’的C语言”),作为C语言的增强版出现。随后,C++不断增加新特性。虚函数(virtual function)、运算符重载(operator overloading)、多继承(multiple inheritance)、标准模板库(standard template library, STL)、异常处理(exception)、运行时类型信息(Runtime type information)、名字空间(namespace)等概念逐渐纳入标准。1998年,国际标准组织(ISO)颁布了C++程序设计语言的第一个国际标准ISO/IEC 14882:1998。根据《C++编程思想》(Thinking in C++)一书,C++与C的代码执行效率往往相差在±5%之间
C++语言发展大概可以分为三个阶段:第一阶段从80年代到1995年。这一阶段C++语言基本上是传统类型上的面向对象语言,并且凭借着接近C语言的效率,在工业界使用的开发语言中占据了相当大份额;第二阶段从1995年到2000年,这一阶段由于标准模板库(STL)和后来的Boost等程序库*的出现,泛型程序设计在C++中占据了越来越多的比重。当然,同时由于Java、C#等语言的出现和硬件价格的大规模下降,C++受到了一定的冲击;第三阶段从2000年至今,由于以Loki、MPL(Boost)等程序库为代表的产生式编程和模板元编程的出现,C++出现了发展历史上又一个新的高峰,这些新技术的出现以及和原有技术的融合,使C++已经成为当今主流程序设计语言中最复杂的一员。(摘抄自维基百科《C++》)

C++特有的命名空间

通过代码理解基本概念

通过对下面这段代码的解释来理解C++的命名空间是什么以及如何使用

#include <iostream>
using std::cout;//命名空间(std)里的实体(cout)的使用,方法一
using std::endl;

int num = 1000;
namespace B//命名空间的声明
{
	int num = 111;
}
namespace A
{
	int num = 10;
	void displayA()
	{
		cout << "displayA"<< endl;
		cout << "displayA B::num: " << B::num << endl;//命名空间B里的实体num的使用,方法二
	}
}

namespace B
{
	void displayB(int num)
	{
		cout << "形参num: " << num << endl;
		cout << "A::num: " << A::num << endl;//存在多个命名空间有相同的实体名称
		cout << "displayB" << endl;
		cout << "B::num: " << B::num << endl;//存在多个命名空间有相同的实体名称
		cout << "::num : " << ::num << endl;//匿名的命名空间访问
	}
}

int main(void)
{
	using A::displayA;
	using B::displayB;
	cout << A::num << endl;
	displayA();
	displayB(11);

	return 0;
}

命名空间的声明:
namespace Name
{
	代码段
}
命名空间里的实体的使用:

方法一:

using 命名空间的名称::要是用的该命名空间的实体(定义在命名空间里的所有变量与函数都称为实体)名称;

之后再操作该实体时,就可以像操作局部变量一样,直接使用该实体的名称进行操作。
方法二:

命名空间的名称::实体名称

这两个方法各有优劣,方法一的优点是代码书写更见简约,但是缺点是如果有多个命名空间有相同名称的实体,那么使用该方法就会出错。方法二可以避免这种错误,但是书写起来相对麻烦些。

特殊场景下的命名空间使用
场景一:存在多个命名空间有相同的实体名称(参考上面的代码段)

解决办法就是使用命名空间里的实体的使用->方法二就可以解决了。

场景二:多个命名空间存在相互调用(参考上面代码段)

解决办法就是可以先在上面定义命名空间的部分实体(循环调用需要用到的)然后再在循环调用的命名空间的下面定义该命名空间其他的实体就可以了。很难说清楚,具体举个例子。拿上面的代码段来说。

namespace A
{
	int num = 110;
	cout << "B::num = " << B::num << endl;
	cout << "A::num = " << A::num << endl;
}
namespace B
{
	int num = 1100;
	cout << "B::num = " << B::num << endl;
	cout << "A::num = " << A::num << endl;
}

这段代码无法通过编译的原因在于,命名空间A需要使用命名空间B的实体num。但是命名空间B的声明在命名空间A的下面。故会报错。解决办法就是:

namespace B
{
	int num = 1100;
}
namespace A
{
	int num = 110;
	cout << "B::num = " << B::num << endl;
	cout << "A::num = " << A::num << endl;
}
namespace B
{
	cout << "B::num = " << B::num << endl;
	cout << "A::num = " << A::num << endl;
}

通过上面的解决办法可以看到命名空间是可以分开声明的,但是分开声明时,不能出现重复定义同一个实体

场景三:全部变量与局部变量重名——匿名空间的使用:

解决办法就是使用匿名空间,举例如下:

int num = 110;//全局变量
int main()
{
	int num = 1100;
	cout << "局部的num = " << num << endl;
	cout << "全局的num = " << ::num << endl;
}

匿名空间的范围就是在当前文件中出现的所有实体以及include包含的头文件中的实体。匿名空间的使用就是直接在实体名称前面加上"::"。

C++的const关键字与C的define

通过代码理解基本概念
#include <iostream>
using std::cout;
using std::endl;


#define Max 1024

int main()
{
	cout << Max << endl;
	const int a = 21;//const常量必须要进行初始化,不然会出错
	//int *pa = &a;出错,类型不同,&a的类型是 const int*,pa的类型为int*
	//a = 110;const 常量值不能被修改

	cout << a << endl;
	int b = 10;
	int c = 11;
	const int * pb = &a;//常量指针(pointer to const)
//	*pb = 10;出错:const int*的变量为只读的,不可以修改指针所指地址的内容,
//	但是可以修改指针所指的地址
	pb = &b;
	int * const pc = &b;//通过int * const(指针常量(const pointer))是可以
//	修改指针所指内容的值,但是,不可以修改指针所指的地址

	*pc = 2;
//	pc = &c;出错:不能改变pc所指向的地址
	const int * const pd = &a;//pd既不能修改指向的地址,也不能修改指向地址
	//的内容

	return 0;
}

const是用来定义常量的关键字。如:

const int y = 110;
or
int const y = 110;

之后y的值讲始终为110,不能被改变。如果改变其值,就会编译报错。错误信息为:assignment of read-only variable ‘y’。

const与指针结合
常量指针(指向常量的指针):
int a = 110;
const int * pa = &a;
a = 120;  //报错
int b = 120;
pa = &b; //OK

常量指针所指向的内容是不可以被修改的,但是可以修改指针所指的地址。

指针常量(指向地址不变的指针)
int b = 120;
int * const pb = &b;
b = 110; //OK
int a = 110;
pb = &a;//报错

通过指针常量是可以修改指针所指地址的内容,但是,不可以修改指针所指的地址。
在C中类似于指针常量/常量指针概念的还有:指针数组/数组指针 、 指针函数/函数指针:指针数组:数组的元素为指针(int * p[N]),数组指针:指向数组的指针(int (p)[N]);指针函数:函数的返回值是指针(int * p(),p为返回值为int的无参函数),函数指针:指针的指向地址为函数(int (*p)(),p为指向返回值为int的无参函数的指针)。

define与const关键字的差别:

1.宏定义发生在预处理阶段。预处理->编译->链接->可执行程序在预处理阶段对宏定义做了字符串的替换,对头文件展开,不涉及到对宏定义
类型的检查。有可能将错误发生的时机延后到运行时;
2.使用const关键字进行定义,发生的时机就是在编译时,具有类型检查的功能;
3.尽量使用const替换宏定义;

C++的new/delete与C的malloc/free

通过代码理解基本概念
#include <stdlib.h>
#include <iostream>
using std::cout;
using std::endl;

int main(void)
{
	int * p1 = (int *) malloc(sizeof(int));//开辟一个堆空间
	*p1 = 10;
	cout << "*p1 = " << *p1 << endl;
	free(p1);//回收p1所指向的堆空间

	int *p2 = new int(1);
	cout << "*p2 = " << *p2 << endl;

	delete(p2);
	//开辟数组空间
	int *p3 = (int*)malloc(sizeof(int)*10);
	int *p4 = new int[10]();
	for(int i = 0; i < 10; i++)
	{
		cout << p4[i] << endl;
	}
	delete[] p4;//new一个数组删除时的写法需要加上[]
	free(p3);
	return 0;
}

c++的new/delete是在堆空间上申请/撤销空间,其使用举例如上代码。

malloc/free 与 new/delete的差别在哪里?

1.malloc/free是C的标准库函数,而new/delete是C++的表达式;
2.malloc在开辟空间时,并不会对空间进行初始化,new表达式在开辟空间时是可以进行初始化的。new一个数组时数组的元素只能初始化为0,(也是默认初始化为0)。

C++的引用(&)与C的指针

通过代码理解基本概念
#include <iostream>
using std::cout;
using std::endl;

//int & ref = a;中的&是引用符号;
//&a = 10;中的&是取地址符号
//引用的作用就是当ref的值改变后,
//a的值也会随之改变。
//引用必须要初始化,不然就会编译出错。
//一旦引用初始化,就不能再被赋值。
//引用可以理解为指针常量
//对引用进行操作,就是对引用指向的变量进行操作
//传参的本质是复制,即:形参与实参分别放在
//不同的栈中,形参是实参的复制。

/*void swap(int x, int y)
{
	int temp = x;
	x = y;
	y = temp;
}
*/
void swap(int *x, int *y)
{
	int temp = *x;
	*x = *y;
	*y = temp;
}
//通过引用实现两个值的互换
//在调用时可以有两种形式
//swap(a,b)
//swap(&a,&b)
//使用引用传参的好处:
//在参数传递时,可以减少复制的开销(为什么?因为其操作的
//对象就是实参本身),提高程序的执行效率
//使用起来更加直观
//引用的底层实现还是指针
//引用与指针的差别:
//指针是一个独立的实体,而引用是一个别名
//指针不需要进行初始化,但是引用必须要进行初始化
//指针是可以改变指向的,引用一旦初始化就不能改变指向
//指针可以设置为NULL,引用不可以

int arr[5] = {0,1,2,3,4};
//引用作为函数的返回值,其在return语句中是不会进行复制的,
//而是将实际的变量实体传到调用的函数中。
int & func(int idx)
{
	return arr[idx];
}

//那么返回的变量的生命周期是一定大于函数的
//如果生命周期小于函数,会出现警告,也有可能造成
//程序出错。这里需要注意:是变量的生命周期,而不是变量的作用范围,
//所以,不要返回一个局部变量的引用
/*int & func1(void)
{
	int a = 123;
	return a;
}*/
void swap(int & x, int & y)
{
	int temp = x;
	x = y;
	y = temp;
}
//如果引用所指向的变量不在栈上,而是在堆上那么其生命周期是
//到它被清空存储空间为止
int & func2()
{
	int * p = new int(1);
	return *p;
}

int main(void)
{
	int a = 10;
	int b = 20;
//	cout << "a = " << a << endl;
//	cout << "b = " << b << endl;
	int c = a + func2() + b;//这里会造成程序执行时内存泄漏,
	//因为引用所指向的内存空间没有办法回收。
	//所以不要轻易返回一个堆空间变量的引用
	//除非你已经做好了内存回收的策略
	cout << "c = " << c << endl;
	swap(a,b);
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	swap(&a, &b);
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	int &ref = a;
	int &refb = b;
//	swap(ref, refb);
	cout << "refa = " << ref << endl;
	cout << "refb = " << refb << endl;
	cout << "a = " << a << endl;
	cout << "ref = " << ref << endl;
	ref = 11;
	cout << a << endl;
	cout << ref << endl;
	func(0) = 10;
	cout << "arr[0] = 0" << "arr[0] = " << arr[0] << endl;
	int & re = func2();
	cout << re << endl;
	delete (&re);
	return 0;
}

引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。

int a ;
int &refa = a;

&在此不是求地址运算,而是起标识作用。类型标识符(上面实例的int)是指目标变量的类型。声明引用时必须同时对其进行初始化。引用跟某一变量绑定之后,不能再绑定到其它变量之上。

引用作为函数参数

C语言中函数参数传递是值传递,如果有大块数据作为参数传递的时候,采用的方案往往是指针,因为这样可以避免将整块数据全部压栈,可以提高程序的效率。即,函数调用是的值传递的本质就是复制
但是现在C++中又增加了一种同样有效率的选择,就是引用(&)。swap(int &x, int &y)(上述实例代码中有改函数的示例)

使用引用作为函数参数的优势:

1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象的操作
2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;
3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元(发生复制),且需要重复使用"*指针变量名"的 形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。
4)如果既要利用指针提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用:

int a = 10;
const int &refa = a;//常量引用
引用作为函数返回值

以引用返回函数值,定义函数时需要在函数名前加&用引用返回一个函数值的最大好处是,在内存中不产生被返回值的副本。示例如下:

int x = 110;
int & func(void)
{
	代码段
	return x;
}
必须遵守的规则:

1)不能返回局部变量的引用:使用引用的一个本质原则就是该变量的生命周期一定不小于该函数的生命周期,故返回局部变量是出错的。
2)不能返回函数内部new分配的内存(堆空间)的引用。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成内存泄漏。例如:

int & func2()
{
	int * p = new int(1);
	return *p;
}

int main()
{
	int a = 10+ func2()+110;//func2在堆上申请的空间没有清除,故造成了内存的泄漏
	return 0;
}
引用 vs 指针
相同点:

都是地址的概念

不同点:

1) 指针是一个实体,而引用仅是个别名;
2)引用使用时无需解引用(*),指针需要解引用;
3)引用只能在定义时被初始化一次之后不可变,指针可变;
4)引用不能为NULL,指针可以为NULL

C++/C的强制类型变换

类型变换的应用场景
C的强制类型变换是相对简单,其实现的示例代码如下:

	double d1 = 3.33;
	//强制类型转换
	int ival = (int)d1;//方法一
	int ival1 = int(d1);//方法二

将上面double类型的d1变换为int有两种方法(如上代码)。C语言的类型变换有一个必须要遵守的规则:只能从存储空间多的向存储空间少的类型变换
C++的强制类型变换的就复杂多了。C++的强制类型变换需要借助于标准库里的函数来实现的,这类函数一共有四个:const_cast/dynamic_cast/reinterpret_cast/static_cast这四个函数的使用场景想要分析清楚还是需要另外下一番功夫的。请参考这个大体了解一下。不过,这四个函数的使用方法是一致的:

const_cast <new_type> (expression)
static_cast <new_type> (expression)
reinterpret_cast <new_type> (expression)
dynamic_cast <new_type> (expression)

下面以static_cast示例吧:

	double d1 = 3.33;
	//强制类型转换
	int ival2 = static_cast<int>(d1);

这样就可以在C++中完成了将double型的d1转换为int型的ival2。

C++的函数重载

首先需要明确的是C是不可以实现函数重载的。C++之所以能够实现函数的重载,原因在于其编译时具有名字改编(name mangling)的功能。解释如下:
示例代码:

@file test.cc
int add(int x, int y)
{
	return x+y;
}

int add(int x, int y, int z)
{
	return x+y+z;
}

int main(void)
{
	int a = 1, b = 2, c = 3;
	add(a,b);
	add(a,b,c);
	return 0;
}

命令行中使用g++ -c test.c来生成test.o文件。然后使用命令nm test.o来查看目标文件test.o。查看如下:
在这里插入图片描述
可以看到目标文件将第一个函数(int add(int x, int y))的名字改写为_z3addii了,而第二个函数(int add(int x, int y, intz))的名字改写为_z2addiii。C++的函数名字改编的机制是:函数名+形参类型的首字母。故C++可以实现函数的重载。

C++带默认参数的函数

C是不可以给函数的参数添加默认值的。在C++中是可以的。示例如下:

#include <iostream>
using std::cout;
using std::endl;

int add(int x, int y=0)
{
	return x+y;
}

int add(int x, int y=0, int z=0)
{
	return x+y+z;
}

int main(void)
{
	add(1);
	return 0;
}

C++函数的默认参数需要遵守一个规则:默认值一定要从右向左设置,不然的话编译无法通过;其次还需要注意的问题就是:如果有函数重名且设置了默认参数时就可能出现函数调用的二义性问题。例如上面的代码在main中调用了add(1)函数时,编译器就没有办法确定程序到底是要调用第一个函数还是第二个函数。

C++的bool类型

C++中有专为布尔类型设计的数据类型:bool。其使用示例如下:

#include <iostream>
using std::cout;
using std::endl;

//在C中没有一个标准的布尔类型(_BOOL)
//C++中的布尔类型就是bool
int main(void)
{
	bool b1 = 100;
	bool b2 = -100;
	bool b3 = 0;
	bool b4 = true;
	bool b5 = false;

	cout << "b1 = " << b1 << endl;
	cout << "b2 = " << b2 << endl;
	cout << "b3 = " << b3 << endl;
	cout << "b4 = " << b4 << endl;
	cout << "b5 = " << b5 << endl;
	cout << sizeof(bool) << endl;
	return 0;
}

输出结果为:

1
1
0
1
0

C++/C的字符串类型

C++的字符串是类,而C的字符串则是数据类型。之所以抛弃C风格字符串而选用C++标准程序库中的string类,是因为string和C风格字符串相比,不必担心内存是否足够、字符串长度,结尾的空白符等等。string作为一个类出现,其集成的成员操作函数功能强大,几乎能满足所有的需求,从另一个角度上说,完全可以把string当成是C++的内置数据类型,放在和int、double等同等位置上。

C++字符串的基本使用
声明一个C++字符串
	std::string s1 = "hello";//第一种声明办法。"hello"字符串的类型为const char *
	//所以,这一句的作用就是将C风格的字符串变为C++风格的字符串
	std::string s2(",world");//第二种声明办法

C++字符串使用前一定要包含C++的string库,即:#include

C与C++字符串的相互转换
C字符串转为C++字符串
std::string s1 = "hello";//"hello"字符串的类型为const char *
	//所以,这一句的作用就是将C风格的字符串变为C++风格的字符串
C++字符串转为C字符串
//s4为C++风格的字符串
	const char * strp = s4.c_str();//方法一
	const char * strp1 = s4.data();//方法二
C++字符串的基本操作
字符串的拼接
//s1,s2,s3,s5都为C++字符串
	std::string s3 = s1+s2;
	std::string s5 = s1+s2 + "shanghai";//C++字符串可以在后面添加const char*类型的字符串
查看字符串的大小
//s3为C++字符串
	cout << "s3.size() = " << s3.size() << endl;//方法一
	cout << "s3.size() = " << s3.length() << endl;//方法二
查看/修改字符串的元素
//像C一样查看string的元素
	for(size_t idx = 0; idx != s3.size(); ++idx)
	{
		cout << s3[idx] << endl;
	}
截取C++字符串
std::string s4 = s3.substr(6,5);//截取s3从第6个元素开始的往后5个元素,并将着5个元素赋给s4字符串。

C/C++的混合编程

理解下面代码:

///
 /// @file    extern.cc
 /// @date    2019-02-03 14:14:35
 ///
 
#include <iostream>
using std::cout;
using std::endl;
//希望第一个函数不进行名字改编,就按照c的方式进行调用,可以使用extern "C"来实现
#ifdef __cplusplus  //这一句话的意思就是如果该文件是C++文件,那么预处理时需要处理
//#(ifdef __cplusplus {中间代码段}#endif)中间代码段。
extern "C"       //extern关键字的作用就是将中括号内的代码段是C编译器编译。
{
#endif
int add(int x, int y)
{
	return x+y;
}
#ifdef __cplusplus
}
#endif
//#ifdef __cplusplus{中间代码段}#endif
//语句可以用来实现C/C++的混合编程。即
//若果定义的文件是C++文件,则在预处理时
//添加上{中间代码段}
//如果定义的是C文件,则在预处理时不用加上
//{中间代码段}

int add(int x, int y, int z)
{
	return x+y+z;
}

int main()
{
	add(1,2);
	add(1,2,3);
}

C++的inline用法

理解下面的代码:

///
 /// @file    inline.cc
 /// @date    2019-02-03 14:57:58
 ///
 
#include <iostream>
using std::cout;
using std::endl;

//inline的作用就是实现了在预处理阶段就把
//func函数写到main函数里面了,这样的好处就是
//在main调用func函数时,直接在main的栈中执行了,
//而不用再重新加载func函数的栈,然后再返回到
//main函数中,从而提高程序的执行效率。
//其本质就是带有参数的宏定义。该功能咋C中并不具有
inline void func()
{
	cout << "func" << endl;
}

int main()
{
	func();
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值