【面向对象】C++对C的改进

在这里插入图片描述# 1. 变量的类型

1.1 变量的定义位置

C变量的定义必须在作用域的开始位置,而C++可以在任何位置。

1.2 命名空间

1.2.1 命名空间概述

1.2.1.1 C语言中
  1. C中同个文件可以定义多个同名的全局变量,只要不超过一个初始化就不会报错,多个同名全局变量也是同一个内存地址,编译器默认未初始化的为变量的声明;
  2. C中只有一个全局作用域,所有的全局变量共享一个全局作用域,因此初始化的全局变量之间不能同名;
1.2.1.3 C++中
  1. C++提出了命名空间的概念,将全局作用域分成不同的部分,不同命名空间的变量可以同名而不会冲突,命名空间可以相互嵌套;;
  2. C++的全局作用域为默认的命名空间;,因此即使未初始化,也不允许同个文件定义多个同名全局变量;

1.2.2 使用方法

  1. 通过namespace 空间名称 { 定义变量} 创建命名空间;
  2. 通过using namespace 空间名称使用命名空间,using name :: variable使用命名空间中的变量,::variable使用默认命名空间的变量;
int a = 10; 					//定义全局变量
namespace first					//定义命名空间first
{
	int i = 10;
}
namespace second				//定义命名空间second
{
	int i = 20;
	namespace third				//嵌套定义命名空间third
	{
		int i = 30;				//third命名空间中定义变量
		typedef struct str {
			int i;
		}str;
	}
}
int main()
{
	using namespace first;					//使用first中的所以变量
	printf("i = %d\n", i);					//输出为10, 因为使用的时first中的i
	using namespace second;
	printf("i = %d\n", i);					//报错,无法确定使用first还是second的i
	using namespace second::third;
	str p = {40};
	printf("i = %d\n", i);					//报错,无法确定使用first还是third的i
	printf("a = %d\n", ::a);				//输出为10
	printf("i = %d\n", second::i);			//输出为20,使用的时second中的i
	printf("i = %d\n", second::third::i);	//输出为30
	printf("i = %d\n", p.i);				//输出为40
}

1.3 默认类型

  1. func()int func(viod)的区别:C++不支持默认类型
  2. C中func()对参数个数没有限制,参数和返回值的类型默认都为int型,func(void)则无法传参,返回值也必须定义;
  3. C++中func()func(void)的含义相同,即都无法传参,参数列表为空;
func1()				//C编译器不会报错,C++编译器会报错,要求必须定义返回值类型
{
	return 10;
}
func2(void)			//在C++中与func2()含义相同
{
	return 5;
}
int main(int args, char * argv[])
{
	func1(1, 2, 3, 4);	//C编译器不会报错, C++编译器会报错
	func2();
}

1.4 布尔类型

  1. C语言中无bool类型 ,而是通过0与非0来代替;
  2. C++中的布尔类型
    (1)C++中bool类型是一个普通的基本数据类型,在内存中占用1个Byte,取值范围为true(数值为1来表示)和false(数值0来表示),可以用来定义数组、全局变量等;
    (2)bool类型可以进行正常的算数运算,但由于只有1和0两个值,因此除了0之外的值都算作1,本质与C相同;
int main(void)
{

	bool b = 0;
	printf("%d\n", b);	//输出为0;
	b++;
	printf("%d\n", b);	//输出为1;
	b = 93;
	printf("%c\n", b);	//输出依然为1	
	printf("%d\n", b);	//输出ascll码表中顺序为1的符号
}

1.5 字符串

1.5.1 基本概述

C语言中不存在真正的字符串类型,而是通过字符数组的方式来实现的

  1. C++中为了兼容C语言,因此也不存在字符串类型
  2. 由于C++可以自定义类,因此C++当中可以通过定义类的方式,来实现同字符串类型完全相同属性的类型;
  3. 通过C++中标准库中提供的字符串类就可以实现字符串排序、比较大小、字符串查找和提取等操作;
    注意:string是类,因此其后定义的是string类的对象,而非string类型的变量,虽然使用上相同,但本质不同。
void string_sort(string str[], int len)			//比字符串比较函数
{
	for (int i = 0; i < len; i ++)
	{
		for (int j = i; j < len; j ++)
		{
			if (str[i] < str[j])				//直接比较字符串的大小
			{
				swap(str[i], str[j]);
			}
		}
	}
}
string string_add(string str[], int len)		//字符串相加函数
{
	string ret;
	for(int i = 0; i < len; i ++)
	{	
		ret += str[i] + ";";					//直接进行字符串之间的相加
	}
	return ret;
}

int main()
{
	string str[5] = {"abc",	"bcd", "cde", "def", "efg"};
	string_sort(str, 5);
	for (int i = 0; i < 5; i ++)
	{
		cout << str[i] << endl;
	}
	cout << string_add(str, 5) << endl;
}

1.5.2字符串与数字相互转换

  1. C++中的字符串类中提供了字符串流类,包括输入流与输出流,用来字符串与数字之间的相互转化。
  2. 需要包含字符串流类头文件<sstream>
#include <sstream>						//包含字符串流类文件
int main()
{
	istringstream s1("123.45");			//字符串输入流
	double num;
	s1 >> num;							//字符串转为数字,返回值是bool,判断是否右移成功
	cout << num << endl;

	ostringstream s2;					//字符串输出流
	s2 << 543.21 << 432.34;						//数字转换为字符串, 返回值是字符,表明可以连续的转换
	string str = s2.str();		
	cout << str << endl;
}

1.6 三目运算符

  1. C中三目运算返回的是变量的数值是常量,而C++中默认返回的是变量本身;
  2. 若三目运算符两个右值中有一个是常量,则C++返回的值也是常量类型;
int func()
{
	int a = 1;
	int b = 2;
	(a > b ? a : b) = 3 	//C编译器会报错, C++不会报错
	printf("b = %d\n", b);	//C++输出b = 3;
	(a > b ? a : 2) = 3;	//C++会报错	
}

1.7 新型的类型转换

1.7.1 C的强制类型转换

  1. 由于C语言中对强制类型转换无限制,对于任何类型都可以进行强制类型转换,太过于粗暴;
  2. 在大型的C程序中,无法定位出强制类型转换的地方;
  3. 以下程序强制类型转换将会报警告,且结果无法预计;
int main()
{	
	struct ps{				//定义结构体
		int i;
		char *name;
	};
	typedef int (pf)(int);	//定义函数类型
	int a = 0x12345678;		
	char c = 'c';
	pf *f = (pf *)a;		//将int型a转为pf型函数赋给函数f 
	c = (char) a;			//将int型a转为char型字符赋给字符c
	ps *s = (ps *)a;		//将int型a转为ps型结构体赋给结构体指针s	
}

1.7.2 C++类型转换

  1. static_cast用于基本类型之间、用于由继承关系类类对象之间和类指针之间;不能用于普通指针之间;
int func()
{
	int i = 0x12345678;
	char c = 'c';
	int *pi = &i;					//定义int*指针
	char *pc = &c;					//定义char*指针
	c = static_cast<char>(i);		//成功,将int型i转为char型赋给c
	*pc = static_cast<char *>(pi);	//失败,static_cast不支持指针类型之间准换
}
  1. const_cast消除变量的只读属性,仅用于const修饰的指针之间和引用之间;
int func()
{
	const int &x = 1;					//内存分配了一块内存,里面存放着1这个数值,由于1时常量,因此其引用x前必须加const表示这个内存中是常量无法修改;
	int &y = const_cast<int &>(x);		//取消了只读属性只读属性,表示这块内存可以修改
	y = 5;
	printf("x = %d\ny = %d\n", x, y);	//输出为x = 5, y = 5;
	
	const int i = 1;					//使用了const修饰,将i和1放到了符号区,并在内存中分配了一块只读的空间但并未使用		
	int &j = const_cast<int &>(i);		//将内存中的空间取消了只读属性,并使j成为了该i内存的应用,符号区的i并未改变
	j = 5;					
	printf("i = %d\nj = %d\n", i, j);	//输出为i = 1, j = 5;
	
	int a = 1;
	int b = const_cast<int>(a);			//报错,因为const_cast不能用于基本类型之间的转换
  1. reinterpret_cast用于指针类型之间的转换;用于指针与整型之间的转换
int main()
{
	int i = 0x12345678;
	char c = 'c';
	float f = 3.14;
	int *pi = &i;
	char *pc = &c;
	pc = reinterpret_cast<char *>(pi);	//成功
	pi = reinterpret_cast<int *>(i);	//成功
	pi = reinterpret_cast<int *>(f);	//报错,不支持浮点型转换
    c = reinterpret_cast<char>(i);		//报错,不能用于基本类型之间的转换
}
  1. dynamic_cast用于有继承关系的类指针之间的转换、有交叉关系的类指针之间的转换、具有类型检查的功能、需要虚函数的支持;

2. 关键字

2.1 register关键字

  1. register关键字的作用是希望编译器尽可能将该变量存储在寄存器中。
  2. C中无法访问register修饰的变量地址,因为访问地址只是相对于内存而言,寄存器不在可访问地址范围内;
  3. C++中可以访问register修饰变量的地址,因为C++编译器编译时,会将register关键字去掉,现在编译器会自动优化决定是否放在寄存器中;
/*
* C中register起作用,无法访问其修饰变量的地址
* C++编译器会自动优化掉register,可以访问该变量地址
* /
int func(void)
{
	register int a = 10;
	int *p = &a;		//在C编译器会报错,C++编译器不会报错
	printf("%p\n", p);	//C++编译器胡将a的地址打印出来
}

2.2 struct关键字

  1. Struct关键字用来定义结构体类型
  2. C中定义结构体变量时,需要在前面加struct,若要去掉struct需用typedef重新定义类型名;
  3. C++定义的结构体变量可以去掉struct关键字,直接用该类型名;
struct str {
	char *name;
	int name;
};
int func(void)
{
	struct str a1;		//C编译器和C++编译器都支持
	str a2;				//C编译器会报错,C++编译器不报错
}

2.3 const关键字

2.3.1 在C语言中

  1. const关键字在C中,修饰的局部变量具有只读属性而并非成为常量,只是在编译器层面而非内存层面上的只读,局部变量可以通过解指针的方法修改成功;
  2. 通过解指针的方法修改全局变量则会出现段错误,因为const修饰的全局变量存储在只读数据段;

2.3.2 在C++中

2.3.2.1 修饰普通变量
  1. const修饰的变量在编译时会将该变量放到符号区成为常量,同时在内存中生成一块特定的区域,此时通过解指针访问的实际上是特定的内存区域,而并未真正的访问符号区const修饰的变量;
  2. 当程序运行时需要该参数时,编译器会到符号区调用该参数,从而保证了该参数是常量;
  3. const关键字修饰的变量的初始值是另一个变量时,被修饰的变量为只读变量而不是常量;
  4. const修饰的变量初始值为别的类型变量时,则生成一个新的变量,新变量与原来变量无关。
  5. constvolatile同时修饰一个变量时,该变量是只读变量而不是常量
const int i = 10;				//C中为常量
int main()
{
	/*****判断a为常量****/
	const int a = 10;			//C中为只读变量,C++中为常量
	int *pa = const_cast<int *>(&a);
	*pa = 5;
	printf("a = %d\n", a);		//C中输出5,C++中输出为10
	printf("*p = %d\n", *pa); 	//C和C++都输出为5
	/*****判断d是只读变量而非常量******/
	int b = 5;	
	const int d = b;			//通过变量来初始化一个const修饰的变量,得到d时只读变量而非常量 
	int *pd = const_cast<int *>(&d);
	*pd = 5;
	printf("a = %d\n", d);		//C中输出5,C++中输出为10
	printf("*p = %d\n", *pd); 	
	/*******判断e是新生成的只读变量*******/
	char c = 'a';
	const int e = c;
	int *pe = const_cast<int *>(&e);
	c = 'v';
	printf("e = %c\n", e);
	printf("c = %c\n", c);
	/*******判断f为只读变量*******/
	volatile const int f = 10;
	int *pf = const_cast<int *>(&f);
	*pf = 5;
	printf("f = %d\n", f);		
	printf("*p = %d\n", *pf); 	//输出为5
}
2.3.2.2 修饰引用
  1. const引用:int b = 10; const int &b = a; ,即可以通过const引用来修改已定义变量的只读属性;
  2. const引用的是一个常量时const int &b = 1,编译器会为这个常量分配一个内存空间,内存空间的别称是这个引用,此时若无const则会报错误,因为引用的是常量,需要const修饰;
  3. const引用的是一个常量时,引用与常量的关系类似于C中字符串与指向字符串的指针之间的关系;

2.4 inline关键字

  1. C++的内联函数作用与C的相同,都是将函数原地展开,即节省了调用函数的开销,提高了效率,又可以通过编译器检查类型等错误;
  2. 内联函数的不同环境
    (1)现在编译器即使无inline关键字,也会可能自动优化为内联函数,因此对于想要内联的函数必须不能复杂;
    (2)inline只是一种请求非强制要求,编译器可能会拒绝;对于不同的环境的编译器,一般含有自己独特的inline关键字;

2.5 newdelete关键字

  1. C语言中通过malloc库函数进行动态内存分配,申请堆的内存空间,通过free释放;C++中通过new关键字来申请堆的空间,通过delete释放;

  2. C语言中malloc是库函数,而非C语言自带关键字,因此在某些简易环境中可能没有malloc函数,无法申请堆内存;C++中new是自带关键字,不受运行环境影响;

  3. malloc函数申请的内存大小是以字节为单位;而new关键字是以数据类型为单位;

  4. melloc只是申请了一块空的内存空间;new申请的空间里存放有对象,new关键字申请单个变量空间时,可以对该空间的变量进行初始化。free只是释放空间,而不会销毁对象,容易造成内存泄漏;delete可以调用析构函数销毁对象并释放空间。
    注意:初始化时无法进行隐式类型转换

int main()
{
	int *pi = new int(1);			//申请堆内存并初始化
//  int *pc = new char('c');		//会报错,初始化时无法进行隐式类型转换 
	char *pc = new char('c');
	float *pf = new float(3.14f);
	int *pa = new int[10];
	printf("*pi = %d\n", *pi);		//输出 pi = 1
	printf("*pc = %c\n", *pc);		//输出 pc = c
	printf("*pf = %f\n", *pf);		//输出 pf = 3.140000
	delete pi;						//释放空间
	delete pc;
	delete pf;
	delete []pa;
}

2.6 extern关键字

2.6.1 C++标准库

2.6.1.1 概述
  1. C++中本身并不包含任何库文件,所有的库文件都是由编译器提供;
  2. 尽管C++标准库中涵盖了C库,但各编译器厂家又都额外增加了C语言兼容库。在这里插入图片描述
2.6.1.2 C++标准库的使用
  1. C++标准库是由类库和函数库组成,标准库中的类和对象都位于std命名空间中;
  2. .h后缀是C库的头文件,C++标准库中无需带.h后缀,其涵盖了C库的功能;
#include <iostream>					//包含C++标准库头文件
#include <cmath>

using namespace std;				//使用命名空间

int main()
{
	double a = 3;
	double b = 4;
	double c = sqrt(a * a + b * b);
	cout << "c = " << c << endl;	//使用C++标准库函数
}

2.6.1 extern关键字

  1. C++编译器可以兼容C编译器的编译方式,但会默认使用C++编译方式,因此当同一个文件中既有C和C++时,可以通过extern“C”{}关键字来强制{}内的内容用C的编译方式编译;
文件:add.c
int add(int a, int b)
{
	return a+b;
}
文件:add.h
int add(int a, int b);
编译:gcc -c add.c -o add.o

文件:add.cpp
#include "add.h"
int main()
{
	printf("%d\n", add(1, 2));
}
编译:g++ add.cpp add.h -o add
./add				//报错误,无法找到add.h,因为add.h包含的add函数是通过C代码,而C++默认用C++编译方式

修改:extern关键字
extern "C"			
{
#include "add.h"			//C++编译器会用C的方式编译
}
int main()
{
 printf("%d\n", add(1, 2));
}
编译:g++ add.cpp add.h -o add
./add 					//此时可以正常执行
  1. 条件编译判断编译环境
    可以通过条件编译的方法,判断_cplusplusC++内嵌的宏来自动确定编译环境,让C代码自动根据编译环境进行编译
#include <stdio.h>
#ifdef __cplusplus
extern "C"
{
#endif
#include "ctest.h"
#ifdef __cplusplus
}
#endif
int main()
{
 int c = add(1, 2);
 printf("%d\n",c);
 return 0;
}

3. 变量的引用

3.1 引用本质

  1. C++中的引用可以看作是一个变量的别名,与原变量名具有相同的作用,
  2. 语法类型名 &引用名 = 变量名
int func()
{
	int a = 5;
	int &b = a;
	b = 10;
	printf("a = %d\n", a);		//a和b的输出结果相同
	printf("b = %d\n", b);
	printf("&a = %p\n", &a);	//a和b的地址值相同
	printf("&b = %p\n", &b);
}

3.2 引用的原理

  1. 引用作为变量的别名,原理上是通过指针来实现的,引用本身占用4Byte的内存空间,int &b = a; <==>int *const b = &a;
  2. 由于引用直接指向的是变量的内存空间,因此可以像指针一样通过形参直接访问实参地址上的值。
void func1(int &a, int &b)		//引用形参不需要初始化
{
	int c;
	c = a; a = b; b = c;
}
void func2(int *a, int *b)
{
	int c;
	c = *a; *a = *b; *b = c;
}
int main()
{
	int a = 1, b = 2;
	int c = 1, d = 2;
	func1(a, b);		//正确调换a和b的位置				
	func2(&c, &d);		//正确调换a和b的位置
	printf("a = %d\tb = %d\n", a, b);
	printf("c = %d\td = %d\n", c, d);
}
  1. 引用也可以作为返回值
int& func1()
{
	int a = 0;
	printf("%d\n", a);
	return a;
}
int& func2()
{
	static int b = 1;
	printf("%d\n", b);
	return b;
}
int main()
{
	int &n1 = func1();			//输出0
	int &n2 = func2();			//输出1
	printf("n1 = %d\n", n1);	//输出为乱码,因为n1是局部变量a的引用,调用结束后,a的地址就内释放了,因此n1指向的原a的地址实际为野指针
	printf("n2 = %d\n", n2);	//输出为1, 因为n2是静态局部变量b的引用,生命周期是整个程序
	n1 = 5;				//因为此时n1指向的是一个栈中未知地址,因此此次赋值无意义
	n2 = 10;
	func1();			//输出为0
	func2();			//输出为10
	printf("n1 = %d\n", n1);	//依然是乱码
	printf("n2 = %d\n", n2);	//输出为10
}
  1. 引用数组是错误的,因为数组需要在内存中连续分配的空间,而引用是变量的别称,引用的变量地址取决于作用域,因此不会连续分配,因此引用数组不合法。
int a = 5;
struct str {
	int &x;
	int &y;
	int &z;
}
int func()
{
	int b = 10;
	int *p = new int(1);
	str s1 = {a, b, *p};
	int &arr[] = {a, b, *p}	//报错,因为数组中的元素在内存中是连续分布的,而a,b, *p分别在静态数据区、栈和堆中,所以不能连续分布,所以报错
	delete p;	
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值