C++基础知识

历史

为啥会有C++?因为C语言不适合,于是提出了面向对象(OOP:object oriented programming)的思想。
C++ Primer
C++是一种计算机高级程序设计语言,由 C语言 扩展升级而产生 , 最早于1979年由 本贾尼·斯特劳斯特卢普 在AT&T贝尔工作室研发。
在C语言基础上进行拓展,增加了类的机制,完成了了一个可以运行的预处理程序,称之为C with classes。

四个阶段以及推荐书籍

在这里插入图片描述

正文

命名空间

1、我们自己定义的变量,函数可能跟库里面重名冲突。
2、公司的项目,通常比较大。多人协作,两个同事写的代码会命名冲突。

例如:
如果加上#include <stdlib>会报错,因为其中包含了一个rand函数,会命名冲突

#include <stdio.h>
//#include <stdlib.h>//如果加上这条会报错,因为其中包含了一个rand函数,会命名冲突
int rand = 0;
int main()
{
	printf("hello world\n");
	printf("%d",rand);
	return 0;
}

C没有很好的办法解决。
而C++提出了命名空间(namespace)这个概念,其中可以放函数,类型,变量

#include <stdio.h>
#include <stdlib.h>
//定义了一个叫bit的命名空间 --- 域
namespace hw
{
	int rand = 0;
}
int main()
{
	printf("hello world\n");
	printf("%d",rand);
	//printf("%d", hw::rand);
	
	
	return 0;
}

::是域作用限定符
优先局部域
如果左边是空白,即去全局域寻找变量
例如:

	int a = 0;
	int main()
	{
	int a = 1;
	printf("%d", a);//局部域寻找 打印 1
	printf("%d", ::a);//全局域寻找 打印 0
		}

命名空间的嵌套

namespace N2
{
	int a;
	int b;
	int Add(int left, int right)
	{
		return left + right;
	}
	namespace N3
	{
		int c;
		int d;
		int Sub(int left, int right)
		{
			return left - right;
		}
	}
}
int main()
{	
	N2::N3::Sub(1,1);
	return 0;
}

命名空间合并

有没有一种可能?就是命名空间也会冲突?NO
其实如果有相同的命名空间,他们不会冲突,而是会合并
例如:
在这里插入图片描述

命名空间使用方式

  • 展开命名空间 using namespace hw; 全部展开,用起来方便,但是没有隔离效果了 —慎用
  • 指定命名空间 hw::xxx 能够做到最好的命名隔离
  • 单独展开某一个成员using hw::ListNode 单独展开某一个成员

C++库定义在一个叫std的命名空间中


流运算符

需要展开std命名空间
<<流插入
可以自动识别类型,有些时候cout不如printf方便,所以推荐哪个方便用哪个

>>流提取
int a = 0;
double b = 3.14;
cin >> a >> b


缺省参数

C++支持参数缺省,C语言不支持
在函数声明或定义的时候为函数指定的一个缺省值,如果没有指定实参,则会使用缺省值作为形参。
但是缺省参数不能再函数声明和定义的时候同时出现,推荐写在声明部分。
缺省值必须是常量或全局变量

void Fun1(int a = 0)
{
	cout << a << endl;
}

int main()
{
	Fun1(1);
	Fun1();
	return 0;
}

运行结果是 1 0

全缺省

如果有多个缺省值,缺省值是从左至右缺省;不能跳过

void Fun2(int a = 10, int b = 20, int c = 30)
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl << endl;
}

int main()
{
	Fun2();
	Fun2(1);
	Fun2(1, 2);
	Fun2(1, 2, 3);
	return 0;
}

在这里插入图片描述

半缺省

缺省一部分,并且从右往左缺省,不能从左往右

void Fun2(int a, int b = 20, int c = 30)
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl << endl;
}

int main()
{
	Fun2();
	Fun2(1);
	Fun2(1, 2);
	Fun2(1, 2, 3);
	return 0;
}

函数重载

什么是重载?
函数重载就是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 顺序)必须不同,但是返回值不能作为函数重载的条件(因为调用的时候区分不了),常用来处理实现功能类似数据类型不同的问题。

原理

简单讲解函数重载原理

参数类型不同

int Fun(int a, int b)
{
	cout << "int Fun(int a, int b)" << endl;
	return a + b;
}
double Fun(double a, double b)
{
	cout << "double Fun(double a, double b)" << endl;
	return a + b;
}

int main()
{
	Fun(1, 2);
	Fun(1.0, 2.0);
	return 0;
}

参数个数不同

void Fun()
{
	cout << "Fun()" << endl;
}

void Fun(int a)
{
	cout << "Fun(int a)" << endl;
}

int main()
{
	Fun();
	Fun(1);
	return 0;
}

参数顺序不同

void Fun(char a, int b)
{
	cout << "void Fun(char a, int b)" << endl;
}

void Fun(int a, char b)
{
	cout << "void Fun(int a, char b)" << endl;
}

int main()
{
	Fun(1, 'a');
	Fun('a', 1);
	return 0;
}

特殊情况 — 构成重载,但是有歧义

void Fun()
{
	cout << "Fun()" << endl;
}

void Fun(int a = 0)
{
	cout << "Fun(int = a)" << endl;
}

int main()
{
	Fun();//有歧义
	Fun(1);
	return 0;
}

这种情况会直接报错,代码跑不起来,因为不知道Fun()到底要调用哪个函数


引用

引用应该使用的名称是一个名字,它是一个已经存在的别名。(其实跟显示生活中的外号很像) 可以把这个名字引用为一个名称,就可以引用一个变量名称或变量名称来说明。并且没有开辟新空间

注意:引用类型必须和引用实体是同种类型的

	int a = 10;
	int& b = a;

	printf("%p\n", &a);
	printf("%p\n", &b);

可以看到地址相同
在这里插入图片描述

还可以有多个引用

	int a = 10;
	int& b = a;
	int& c = b;
	int& d = a;

在这里插入图片描述

指针 VS 引用

它们之间有:

  • 不存在空引用。引用必须连接到正常的内存。
  • 引用必须在创建时被初始化。指针可以在任何时间被初始化
  • 有多级指针,但是没有多级引用
  • sizeof意义不一样。指针都是4或者8字节,然而引用却是引用类型的大小
  • 指针一定要显式解引用使用,引用是编译器自己处理
  • 引用一旦引用一个实体,再不能引用其他实体。
int a = 10;
int& b = a;

int c = 20;
b = c;

可以看到地址没变,只是值变了(专一)
在这里插入图片描述

  • 指针可以在那个时候指向另一个对象。
	int a = 10;
	int* pa = &a;
	int* pb = pa;

	int c = 20;
	int* pc = &c;
	pb = pc;

而指针就是值和地址都变了(善变)
在这里插入图片描述

使用场景

做参数
之前如果交换两个值,需要用到指针,现在可以直接传引用,直接交换。

void Swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}
int main()
{
	int a = 1;
	int b = 2;
	Swap(a, b);
	return 0;
}

做返回值
正常传返回值

int Add(int a, int b)
{
	int c = a + b;
	return c;
}
int main()
{
	int a = 10;
	int b = 20;
	int ret = Add(a, b);
	cout << ret << endl;

	return 0;
}

但如果传引用呢

int& Add(int a, int b)
{
	int c = a + b;
	return c;
}
int main()
{
	int a = 10;
	int b = 20;
	int ret = Add(a, b);
	cout << ret << endl;

	return 0;
}

虽然打印的值没有问题,但是其实这里传的返回值是临时变量的引用(即c在栈帧中的引用)
但是栈帧其实已经被销毁了,而VS下栈帧销毁,里面的值没有清空,所以看到的是30。
综上,其实这种方式并不是所有地方都能用

如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已经还给系统了,则必须使用传值返回

其实引用返回还有优势,如果返回的对象很大,或者深拷贝对象的时候,可以提高性能
引用返回还可以改变返回对象

常引用

权限放大,不可行

const int a = 10;//不行,权限放大了
int& b = a;

权限不变

const int a = 10;//权限不变
const int& b = a;

权限缩小

int a = 10;//权限缩小
const int& b = a;

提个问题,以下写法合法吗?

double d = 3.14;
int& i1 = d;

上面不合法

double d = 3.14;
const int& i1 = d;

这种合法
为啥呢?
因为赋值会产生临时变量然而临时变量具有常性,不能被修改,所以直接引用会导致权限扩大,就不合法了。

内联函数

通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。

对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。

如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline,在调用函数之前需要对函数进行定义。如果已定义的函数多于一行,编译器会忽略 inline 限定符。

在类定义中的定义的函数都是内联函数,即使没有使用` inline `说明符。

特性

inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。

inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联。

inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。


auto 关键字

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

auto的使用

注意:使用auto必须要初始化
auto会去掉const属性

  1. auto与指针和引用结合起来使用
    auto声明指针类型时,用autoauto*没有任何区别,但用auto声明引用类型时则必须加&
int a = 10;
auto b = &a;
  1. 在同一行定义多个变量
    当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量
auto a = 1,b = 2;
auto c = 3,d = 3.14;//编译失败

3.范围for

普通遍历数组

int arr[] = {1, 2, 3, 4, 5};
for(int i = 0; i < sizeof(arr)/sizeof(arr[0]) ; ++i)
{
	cout << arr[i] << endl;
}

C++11范围for

//自动依次取arr中的每个元素,赋值给e
for(auto e : arr)
{
	cout << e << endl;
}
//每个数都+1
for(auto& e : arr)
{
	e++;
}

范围for必须是数组名
以下情况是不能使用范围for的,因为数组名已经变成了指针

void Test(int a[])
{
	for(auto e : a)
	{
		cout << a[i] << endl;
	}
}

int main()
{
	int arr[] ={1, 2, 3, 4, 5};
	Test(arr);
	return 0;
}

auto不能自动推导的场景

auto不能做参数,不能声明数组


NULL和nullptr

指针没有合适的值初始化时,我们一般会把它设置为空指针

int* ptr1 = NULL;
int* ptr2 = 0;

其实NULL是一个宏,实现如下

#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

NULL 和 0 在C++可能出现的问题

void test(int* )
{
	cout << "int*" << endl;
}
void test(int )
{
	cout << "int" << endl;
}

int main()
{
	test(0);
	test(NULL);
}

看到结果,发现并没有调用到第一个函数.
在这里插入图片描述
所以C++建议使用nullptr

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

eptcup_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值