C++ hello world 7.18

本文详细介绍了C++中的命名空间、命名空间的使用方法、输入输出函数、缺省函数、函数重载以及引用的概念、用法和区别。重点讲解了如何避免命名冲突,以及如何通过函数名修饰规则实现重载功能。
摘要由CSDN通过智能技术生成

C++ hello world

#include <iostream>
using namespace std;

int main()
{
	cout << "hello world" << endl;
	return 0;
}

我们会逐句的分析这些代码

命名空间namespace

命名空间的概念

在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存 在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化, 以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。

using namespace std;
  • 自己写的库,官方库,第三方库,当项目整合之后,我们不可避免遇到命名重复,冲突的问题
  • 在C语言中,只有一方相让才能避免这个问题的方式,依此推出命名空间这个语法
#include <stdio.h>
#include <stdlib.h>

int rand = 0;

int main()
{
	printf("%d", rand);
	return 0;
}

如上,我们rand与stdlib.h中的rand命名冲突了,

#include <stdio.h>
#include <stdlib.h>

namespace lv
{
	int rand = 1;
}

int main()
{
	printf("%p\n", rand);
	printf("%d\n", lv:: rand);
	return 0;
}

namespace是关键字,后面的lv是自定义的空间名,可以理解为一个类似的结构体

这个时候我们在主函数调用rand,不指定空间的话,默认是全局库里面的rand,即stdlib文件中的

命名空间定义的是一个域,不创建域去定义rand的话就是全局域中命名冲突了,而namespace就是定义了一个新域,新域的使用方法如下

命名空间的三种使用方法

1.加命名空间名称及作用域限定符

bit::rand
namespace lv
{
	int rand = 1;
	
	int Add(int left, int right)
	{
		return left + right;
	}

	struct node
	{
		struct node* next;
		int val;
	};
}
  • 命名空间可以定义变量 函数 类型
  • 命名空间通俗来讲就是在一块空山上,有一个搭建的私人果园,空山就是全局域,谁都可以去,果园就是自己命名的空间,未经许可谁都不能去,::这个操作符就是钥匙许可,有了权限才能进去
注意的是struct Node node的操作符是加在struct后面的
struct bit :: Node node;

2.使用using namespace 命名空间名称引入

如上面,我们一个一个的使用自定义空间的时候都要主动加上域作用操作符,有没有什么办法直接开放权限呢

using namespace bit

这就相当于,把果园的权限全部开放,所有人都可以去采摘

但是这是一个危险的行为,当我们的官方库和bit域中仍然有命名冲突的时候,如rand,编译器就不知道去访问哪个域中的rand了

需要重复写多个一样的代码,并且要指定域情况下,全部展开会危险,我们可以部分展开

3.使用using将命名空间中某个成员引入

授权bit域中的Add,假设全局中也有Add,当编译器编译的时候看见我们对bit域中的Add授权后就会自动去使用bit域中的Add

命名空间可以套娃

namespace lv
{
	int rand = 1;
	
	int Add(int left, int right)
	{
		return left + right;
	}

	struct node
	{
		struct node* next;
		int val;
	};

	namespace xx
	{
		int rand = 0;
	}
}

lv::xx::rand

语句分析

在这里插入图片描述

我们写CPP的时候都喜欢全部展开std空间

在这里插入图片描述

  • std这个域是写在iostream这个头文件里面的,我们展开这个头文件的时候并没有展开std域的权限,默认在全局找
  • iostream头文件的展开是拷贝代码过来,其中包括std域的定义
  • using namespace std是权限的展开,可以不要这句话,但要有头文件,因为域的定义写在头文件里面
int main()
{
	std::cout << "hello" << std::endl;
	return 0;
}
using std::cout;
using std::endl;

int main()
{
	cout << "hello" << endl;
	return 0;
}

注意,一个或多个文件中的同一个命名空间会自动合并

输入输出函数

  • C语言中的输入输出函数是scanf和printf
  • C++的就是cin和cout,in就是输入,out是输出,前面的c是console(控制台)的意思
  • cin就是控制台输入,cout就是控制台输出
  • 这2个函数是包含在iostream头文件里面的,涉及了类和对象,函数重载的知识
cout << "hello world"<< endl;
<<是流插入运算符,hello world流向cout这个对象
endl 是end of line 换行的意思

int i = 0;
cin >> i;
>>是流提取运算符,在控制台提取一个值给到i变量里

在这里插入图片描述

C++的输入输出可以自动识别类型,C语言要加类型符号,这里面用到的是函数重载

缺省函数

缺省的概念

在这里插入图片描述

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

通俗来讲就是备胎的意思

缺省函数的分类

  • 全缺省参数

在这里插入图片描述

在这里插入图片描述

这种不行,只能是从左往右显示传参

  • 半缺省参数

在这里插入图片描述

使用场景

typedef struct stack
{
    int* a;
    int top;
    int capacity;
}stack;

void initst(stack* p)
{
    p->a = NULL;
    p->top = 0;
    p->capacity = 0;
}

void pushst(stack* p)
{
    //.....
}

ok我们很多代码都是这样写的,当我们在主函数里面明确要插入100个数据时候,我们每轮扩容4个,太low了,并且扩容有内存的消耗,很多书上都这样写
   
#define N 4;
void initst(stack* P)
{
    p->a = (int*)malloc(sizeof(int) * N);
    p->top = 0;
    p->capacity = 0;
}

这样写也不能避免扩容问题,而且也不能随着外部需求改变
使用缺省函数即可

void initial(stack* p, int N = 4)
{
     p->a = (int*)malloc(sizeof(int) * N);
     p->top = 0;
     p->capacity = 0;
}

注意,倘若声明和定义分开的话,声明和定义不能同时给缺省参数,声明给定义不给

重载

重载的概念

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

  • 简单来说在C++里面重载就是一词多义的意思

  • C语言是不允许函数同名的情况

  • C++可以,但要求构成重载函数(函数名相同,参数不同)

  • 这里的参数不同,有3种

    1.参数个数不同

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

    2.参数类型不同

    int add(int a, int b)
    {
    	cout << "add(int a, int b)" << endl;
    	return a + b;
    }
    
    int add(double a, double b)
    {
    	cout << "add(double a, double b)" << endl;
    	return a + b;
    
    }
    
    //下面这个也算参数类型不同
    int Add(int left, int right)
    {
    	cout << "int Add(int left, int right)" << endl;
    return left + right;
    }
    
    double Add(double left, double right)
       {
    	cout << "double Add(double left, double right)" << endl;
    	return left + right;
    }
    

    3.参数顺序不同

    void f(int a, char b)
    {
        cout << "f(int a,char b)" << endl;
    }
    
    void f(char b, int a)
    {
        cout << "f(char b, int a)" << endl;
    }
    
    这里的顺序不是形参名的顺序,是类型的顺序
    

在这里插入图片描述

在这里插入图片描述

注意,单纯的返回值不同不能构成重载,在学了重载是如何实现的就知道了

重载函数是如何实现的

在这里插入图片描述

我们的重载函数,编译器是如何对应找到我们的函数的呢?要从C++的编译链接说起

以我们的汇编代码来看,函数的跳转是call 函数地址,在编译链接过程中生成符号表
在这里插入图片描述

在C语言中,函数名充当符号表,有2个一样的重名函数,编译器区分不开

倘若C++继续使用这个函数名充当符号表,那么C++就不支持重载函数了,故C++有一个函数名修饰规则

在这里插入图片描述

如图,将2个重名的函数,修饰成了不同的新名,这样编译器就可以对应找的函数

注意的是,函数的地址是第一句指令的地址,如果只有声明没有定义,编译器就会报错,找不到函数的地址

然而我们的C++的函数名修饰规则不是那么的直观,在Linux下就直观一些了

在这里插入图片描述

_Z4funcid:

  • _Z是前缀
  • 4是函数名的字节大小
  • func是函数名
  • id是形参的首字母,即int double

我们函数的类型不同,修饰出来的名就不同,所以编译器可以清楚的知道我们要调用哪一个函数,这就是重载函数

在这里插入图片描述

这也说明了,单纯的函数返回值不同不能构成重载,因为函数名修饰规则里面没有带返回值,我们主要要看参数的类型

引用

引用的概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空 间,它和它引用的变量共用同一块内存空间。

比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"。

类型& 引用变量名(对象名) = 引用实体

void TestRef()
{
    int a = 10;
    int& ra = a;//<====定义引用类型
    printf("%p\n", &a);
    printf("%p\n", &ra);
}
  • 注意:引用类型必须和引用实体是同种类型的
  • 如上图代码,ra就是a的别名,也就是说a所在的空间有ra和a 2个名字,空间地址一样
  • 对ra操作就是对a操作,反之也是
  • 不能与取地址符号混淆

在这里插入图片描述

引用的使用

做参数

引用在特殊场景下有妙用,如在swap函数中

void swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}

int main()
{
	int a = 1;
	int b = 2;
	swap(a, b);
	cout << a << endl;
	cout << b << endl;
	return 0;
}

我们可以在形参上取别名,即形参就是实参的别名,这样改变形参就可以改变到实参了

再比如说我们的单链表

typedef struct list
{
	int num;
	struct list* next;
}list;

//C语言写法
void pushback(list** node, int x)
{
	list* newnode;
	if (!*node)
	{
		*node = newnode;
	}
	else
	{
		//...
	}
}

//C++写法
void pushback(list*& node, int x)
{
	list* newnode;
	if (node)
	{
		node = newnode;
	}
	else
	{
		//...
	}
}
做返回值

我们先看下面代码

在这里插入图片描述

注意count的n不是直接返回给主函数的,n出栈帧就销毁了,而是存在一个临时变量里

我们的返回值引用

在这里插入图片描述

可能会问,为什么引用可以拷贝给ret,是因为引用的本质是取别名,相当于同一块空间有多个名字,这和n直接拷贝给ret等价

n的空间销毁了,但是ret去访问这块空间,这是危险的

有2种可能,第一种就是1,第二种是随机值

相当于你住酒店,退房前留下一个苹果,退房后再去访问这个房间,苹果还在不在是不确定的,在不同编译器下,有的空间释放就会置为随机值,有的不会,要看具体的环境,如上面的栈帧销毁后,就不会被置成随机值

在这里插入图片描述

如果我们再把ret作为n的引用,调用2次cout打印ret为什么出现一次1一次随机值?

因为cout的本质也是函数,也要建立栈帧,在什么地方建立栈帧呢?在之前count函数的那个空间上(销毁出栈,cout函数压栈),只是开大开小的问题,这样我们的cout函数就会覆盖掉之前n所在的空间,故此是随机值了

但为什么,第一次不是随机值?因为第一次cout调用,ret是作为的参数,那个时候n还没有覆盖掉,第二次才覆盖

那这样呢

在这里插入图片描述

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;
    return 0;
}

在这里插入图片描述

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

增强效率:以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直 接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效 率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。

再比如说我们的链表,我们需要读取,并且修改一个位置的值时候

typedef struct list
{
	int arr[10];
	int sz;
}list;

//读取
int get(list* p, int i)
{
	return p->arr[i];
}

//修改
void change(list* p, int i, int x)
{
	p->arr[i] = x;
}

如图,C语言需要2个接口

int& at(list& p, int i)
{
	return p.arr[i];
}

int main()
{
	list s;
	at(s, 0) = 0;
	at(s, 1) = 1;
	at(s, 2) = 2;
	return 0;
}

C++利用引用只需要一个接口,当然在C语言中用指针也可以完成,但是没有C++这样舒服

在这里插入图片描述

#define N 10

struct node
{
	//成员函数
	int& at(int i)
	{
		return a[i];
	}

	//成员变量
	int a[N];
};

在C++里面struct升级成类了,里面可以定义函数等

int main()
{
    struct node s; //兼容C的定义
    node s;		  //C++可以直接这样定义
    retrurn 0;
}
int main()
{
	node s;
	int i = 0;
	for (i = 0; i < N; i++)
	{
		s.at(i) = i;
	}

	for (i = 0; i < N; i++)
	{
		cout << s.at(i) << " ";
	}
	cout << endl;
	return 0;
}

在这里插入图片描述

引用的条件

  1. 引用在定义时必须初始化
  2. 一个变量可以有多个引用
  3. 引用一旦引用一个实体,再不能引用其他实体

常引用

在这里插入图片描述

如图这样写,编译器不能通过

如图,a是const修饰的,即在语法上不能被修改,而起别名b之后,并没有给b也加上限制,这a可以通过b进行修改,这是权限的放大,是不允许的

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 很多时候都会产生临时变量
  • 临时变量是常量
  • 1中临时变量可以拷贝给ret,不涉及什么权限放大
  • 2中就是权限放大了,一个常量交给一个变量是不合语法的,加上const修饰即可

引用和指针的区别

引用和指针的不同点:

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

我们来看引用和指针的底层

在这里插入图片描述

在底层的角度,引用就是指针

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值