与C++大战三百回合之第一回——C++入门

最好的学习资源是自己写的代码,大家在学习的过程中多自己尝试,不要盲信,以下知识点都是个人拙见,如果不对的地方请一定指出!

目录

1.命名空间

2.缺省参数

3.函数重载

4.引用 &

5.内联函数

6.auto 关键字

7.nullptr 关键字


1.命名空间

众所周知,C++是创始人本贾尼,老本啊,觉得c语言不够好,在这基础上优化出来的一门语言,所以,我们就先以c语言的一个问题来引入。

下图中代码是个典型的命名冲突问题,头文件stdlib在展开的时候里面有一个rand,在定义前的意思为随机值函数,但是又将其定义为了int类型,编译器无法区分,其次,在企业一个项目多个人完成的情况下也可能会出现不同的人写了同样的变量名导致命名冲突的问题,C语言是无法解决这个问题的,想必老本也是深受其扰,所以他就往C++里面加了一个叫做命名空间的东西,目的是对标识符的名称进行本地化,以避免命名冲突和名字污染

#include <stdio.h>
#include <stdlib.h>
//但是如果没有这个头文件,rand就是属于一个普通的变量名,不会报错
int rand = 10;
int main()
{
	printf("%d\n", rand);
	return 0;
}

namespace就是针对该问题的一个关键字

用法:namespace 空间名称 { .... }

namespace bit
{
    int rand = 10;
}

这样这个rand就是独属于空间域bit的变量,编译器是无法直接访问的,编译器默认可以直接访问的先是在当前局部域查找,其次再去到全局域查找,不可以直接到命名空间域查找。变量名在不同域是可以出现同名的,所以是会出现在局部变量和全局变量同名的情况的,而编译规则又是先在局部域查找的,那我们如何直接去使用同名的全局变量呢?

这个时候再引入一个知识点    :: 域作用限定符,::rand就是直接查找到了全局域中的rand

那我们要如何查找自己定义的命名空间里面的变量/函数/类型等等呢?有三个方法

1.指定访问:域名::域中的变量名

namespace bit
{
    int rand = 1;
}
cout << bit::rand;

2.命名空间全展开:using namespace 域名,这句代码类似于一句声明,告诉编译器,你可以来我这里找成员了,因为编译器默认不能来的嘛,所以要声明一句,但是编译器的查找顺序依然先是局部再全局,最后再来展开的命名空间查找。但是如果其他域内也有同名成员,依然会冲突

namespace bit
{
    int rand = 1;
}
using namespace bit;
cout << rand;

 3.指定某一个:using 域名::变量名,这种方法一般适用于一个域内的某个成员需要频繁调用

namespace bit
{
    int rand = 1;
}
using bit::rand;
cout << rand;

 那么讲到这里应该就可以大概猜到为什么C++的代码在开头基本都要加上一句using namespace std了,因为std是官方库中的一个命名空间,我们在C++中最常用的cin,cout,endl这些都是在这个命名空间内的,所以需要展开std这个命名空间。当然,如果这段代码都没有用上这些就完全可以不展开std

2.缺省参数

分为:全缺省和半缺省,半缺省是缺省一部分,但是只能从右往左缺省,缺省参数使用的时候必须按照需求的顺序传参

#include <iostream>
using namespace std;
void func1(int i = 1)
{
	cout << i << endl;
}
void func2(int i = 10, int j = 20, int k = 30)  //缺省多个
{
	cout << i << " " << j << " " << k << endl;
}
//编译错误:“func3”:缺少形参2的默认实参
//void func3(int i = 10, int j, int k = 30)
//{
//	cout << i << " " << j << " " << k << endl;
//}
//半缺省:缺省参数只能从右往左连续的缺省,比如func4
void func4(int i, int j = 20, int k = 30)
{
	cout << i << " " << j << " " << k << endl;
}
void func5(int i = 10); //声明有缺省
//编译错误:func5:重定义默认参数
//void func5(int i = 10)
//{
//	cout << i << endl;
//}
void func5(int i)  //定义的时候就没有缺省
{
	cout << i << endl;
}
int main()
{
	func1();  //默认 i = 1
	func1(2);  //i传为2

	func2(100, 200, 300);
	func2(100);  //只传i,j和k默认

	//func3(100, 200);  报错

	func4(100);
	func4(100, 200);

	func5();
	return 0;
}
//运行结果:
//1
//2
//100 200 300
//100 20 30
//100 20 30
//100 200 30
//10

3.函数重载

在同一作用域中声明的作用类似的同名函数,重载条件:1.处于同一作用域中  2.形参不同(个数,类型,顺序),函数的返回值不是函数的重载条件

#include <iostream>
using namespace std;
//形参个数不同
void f1()
{
	cout << "f1()" << endl;
}
void f1(int a)
{
	cout << "f1(int a)" << endl;
}
//形参类型不一样
void f2(int a, int b)
{
	cout << "f2(int a, int b)" << endl;
}
void f2(int a, double b)
{
	cout << "f2(int a, double b)" << endl;
}
//形参顺序不一样
void f3(int a, double b)
{
	cout << "f3(int a, double b)" << endl;
}
void f3(double a, int b)
{
	cout << "f3(double a, int b)" << endl;
}
//与返回值无关,只需要形参满足条件就属于重载
int f4(int a, int b)
{
	cout << "int" << endl;
	return 0;
}
double f4(double a, double b)
{
	cout << "double" << endl;
	return 0;
}
报错:无法重载仅按返回类型区分的函数
//double f4(int a, int b)
//{
//	cout << "int" << endl;
//}
int main()
{                                //运行结果:
	f1();                        //f1()
	f1(1);                       //f1(int a)
	f2(1, 1);                    //f2(int a, int b)
	f2(1, 2.2);                  //f2(int a, double b)
	f3(2.2, 1);                  //f3(double a, int b)
	f3(1, 2.2);                  //f3(int a, double b)
	int ret1 = f4(1, 1);         //int
	double ret2 = f4(1.1, 2.2);  //double
	return 0;
}

注意:当一个函数为缺省函数时,和同名的无参数函数依照定义也为重载函数,二者形参个数不同,但是可能会有调用歧义,报错为有多个重载函数匹配

 所以要避免二者的同时出现

4.引用 &

引用&在定义时使用的含义为别名,int a = 10  int& b = a,即是b为a的别名,但是不可以理解为a是b的别名,一个变量可以有多个别名,别名也可以有别名,所以 int& c = b 也是可以的,而&符号不在定义时的含义和以前一样,为取地址

别名作为形参的时候,它就是实参本身,因为它是实参的别名,不再是实参的一个临时拷贝,对别名的修改会影响到本体

#include <iostream>
using namespace std;
void func(int& a, int b)
{
	a++;
	b++;
}
int main()
{
	int a = 1;
	int b = 1;
	func(a, b);
	cout << a << ' ' << b << endl;  //结果:2 1
	return 0;
}

任何数据类型对象都可以有别名,无论是自定类型还是啥,包括指针,eg. int*& pr = p1 这里pr就是指针p1的别名

引用在使用的时候必须在定义的时候就初始化,因为引用的意思是别名嘛,要是不立刻初始化好,编译器怎么会知道这个是谁的别名,凭空出现的一个外号,显然不合理

引用是引用一个实体对象,一旦使用了就不可以再引用其他的实体对象

int x = 0;
int& y = x;
int z = 1;
y = z;

例如在上面的代码中,y作为x的引用后,y = z的意思不为y为z的引用,而是z赋值给y,y又是x的引用,所以x也为1,结果x,y,z都为1

如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统,出作用域没有销毁),则可以使用 引用返回,如果已经还给系统了,则必须使用传值返回

#include <iostream>
using namespace std;
int& Add(int a, int b)
{
	int c = a + b;
	return c;
}
int main()
{
	int& ret1 = Add(1, 2);
	int ret2 = Add(1, 2);
	cout << "Add(1, 2) is :" << ret1 << endl;
	cout << "Add(1, 2) is :" << ret2 << endl;
	return 0;
}

以上代码中,ret1是函数Add的引用,而函数Add的返回值是函数中c的引用,而c在出函数的时候会销毁,引用的底层是指针,所以此时那个引用只是保存着那个返回值的地址,并没有值,ret1又作为Add的引用,打印的时候为随机值,ret2是一个变量,引用赋值给对象的时候是直接值复制,直接把a+b的值赋值给了ret2,所以打印为正常值

引用权限的放大,缩小和平移:引用的权限只可以缩小和平移,不可以放大

const int m = 0;
int& n = m;  //不可以,权限放大了,由只读变成了可读可写
const int& n = m;  //可以,权限平移

int x = 0;
const int& y = x;  //权限缩小

在出现隐式类型转换的时候,转换过程中会有临时变量代存值,再将值赋值给新变量,那个临时变量具有常性,所以也会涉及到权限的变化,举个例子

double d = 12.34;
int& a = d;  //报错:发生了隐式类型转换,a有常性
const int& b = d;

引用和指针的区别:

1.引用在语法上不开空间,而指针会开空间,但是在底层上二者都哟开辟空间,但是一般日常中都讨论语法,认为不开

2.引用必须初始化,指针不用

3.不存在指向空值的引用,但是存在指向空值的指针

4.引用不能改变指向,指针可以,所以链表等只能采用指针

5.sizeof不一样:引用为引用类型的大小,指针为地址空间所占字节个数

6.有多级指针但是没有多级引用

7.引用比指针使用起来相对更安全

5.内联函数

小函数频繁调用会消耗时间,所以在c语言中我们一般把频繁调用的小函数设置为宏函数来提高我们反复调用小函数的效率,但是宏函数不仅定义时复杂,而且不好调试,所以老本在C++内新加了一个叫内联函数的东西

inline修饰的函数就是内联函数,修饰过后这个函数在编译时编译器会直接在调用内联函数的位置处展开,不会再在栈帧上建立,从而提升程序运行的效率

inline int Add(int a, int b)
{
    return a + b;
}

特性

1.inline是以空间换时间的做法,编译时用函数体替换函数调用,可能会使目标文件变大,但是没了调用开销,提高了程序效率

2.inline只是对于编译器的一个建议,被修饰的函数是否为内联函数取决于编译器自己的决定,如果这个函数较短,不会递归,且频繁调用,编译器才采用inline修饰,否则会忽略inline的特性

3.内联函数的定义和声明不可以分离,inline展开后是没有地址的,分离的话会导致链接错误

6.auto 关键字

随着程序逐渐复杂,所用到的类型逐渐增多,我们可以使用auto来让编译器自己识别类型,是代码变得简洁,auto是通过等号右值的初始化类型自动推导对象类型的,所以必须要在定义的时候就初始化,否则无法推导,eg. auto i = 1;i会自动推导为int类型,我们也可以验证一下

看到没,typeid().name()是看类型的

auto的类型推导是可以通过添加的限定条件来规定类型

int x = 1;
auto* b = &x;  //*就要求了必须是一个指针,否则无法推导
auto& c = x;   //要求c是x的引用

大概了解了auto的一个功能之后,讲一下auto的一些使用条件:

1.auto不能做函数的参数

2. auto不能用来定义数组

 3.不同类型的auto不能在一块定义

auto a = 4, b = 1.1;
//报错:对于此实体“auto”定义为double,但之前默示为int

auto在范围for中的使用

我们可以用利用auto来遍历数组,但是这种方法只可以正序遍历,别的倒序遍历什么的都不可以

int a[5] = { 1, 2, 3, 4, 5 };
for (auto e : a)
{
	cout << e << " ";
}
cout << endl;
//结果:1 2 3 4 5

变量e自动取数组a中的类型并赋值,再自动++,自动判断结束,这里e只是数组a内成员的赋值,所以无法通过e修改数组a,想修改的话就可以利用到我们前面讲的引用,让e为数组a成员的别名就可以修改了

int a[5] = { 1, 2, 3, 4, 5 };
for (auto& e : a)
{
	e++;
	cout << e << " ";
}
//结果:2 3 4 5 6

但是这种遍历数组的方式不可以在函数内使用,因为数组是不支持传参的,在函数内数组传的是首元素的地址,e无法找到后面的元素

7.nullptr 关键字

在我们使用宏的时候,有时可能会因为需要把NULL定义为常量0,毕竟二者含义相同,但是在面对下面这个情况的时候就可能在使用的时候无法达到自己的预期

#define NULL 0
void f(int);   //f(0);  f(NULL);
void f(int*);

 函数f(0)和 f(NULL)都会指向第一个函数调用

所以老本在C++里面搞了一个关键字 nullptr,非常的好用,它是一个关键字,所以不用包含头文件,所以为了我们的代码安全,以后把我们的NULL都改成nullptr吧!

  • 10
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值