01.C++入门基础

1.命名空间

(1)命名空间的意义

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

(2)namespace定义

基本形式:
namespace 名称{
变量、函数、类的定义
}
代码示例:

#include <stdio.h>  
#include <stdlib.h>  
// 1. 正常的命名空间定义  
// pig是命名空间的名字,⼀般开发中是⽤项⽬名字做命名空间名。  
namespace pig  
{  
	// 命名空间中可以定义变量/函数/类型  
	int rand = 10;  
	int Add(int left, int right)  
	{  
		return left + right;  
	} 
	struct Node  
	{  
		struct Node* next;  
		int val;  
	}; 
} 
int main()  
{  
	// 这⾥默认是访问的是全局的rand函数指针  
	printf("%p\n", rand);  
	// 这⾥指定bit命名空间中的rand  
	printf("%d\n", pig::rand);  
	return 0;  
}

注意点:

  • namespace只能定义在全局,可以嵌套定义。
    代码示例
namespace animal 
{  
 
	namespace pig  
	{  
		int rand = 1;  
		int Add(int left, int right)  
		{  
			return left + right;  
		}  
	}   
	namespace dog  
	{  
		int rand = 2;  
		int Add(int left, int right)  
		{  
			return (left + right)*10;  
		}   
	} 
}
int main()  
{  
	printf("%d\n", animal::pg::rand);  
	printf("%d\n", animal::hg::rand);  
	printf("%d\n", animal::pg::Add(1, 2));  
	printf("%d\n", animal::hg::Add(1, 2));  
	return 0;  	
}
  • 项⽬⼯程中多⽂件中定义的同名namespace会认为是⼀个namespace,会合并到一起,不会冲突。
  • C++标准库都放在⼀个叫std(standard)的命名空间中

    (3)命名空间的使用

    三种命名空间的使用方法:
    • 指定命名空间访问,项⽬中推荐这种⽅式。
    • using将命名空间中某个成员展开,项⽬中经常访问的不存在冲突的成员推荐这种⽅式。
    • 展开命名空间中全部成员,项⽬不推荐,冲突⻛险很⼤,⽇常⼩练习程序为了⽅便推荐使⽤。
      三种方法的代码示例:
#include<stdio.h>  
namespace N  
{  
	int a = 0;  
	int b = 1;  
}  
/ 指定命名空间访问  
int main()  
{  
	printf("%d\n", N::a);  
	return 0;  
} 

// using将命名空间中某个成员展开  
using N::b;  
int main()  
{  
	printf("%d\n", N::a);  
	printf("%d\n", b);  
	return 0;  
}

// 展开命名空间中全部成员  
using namespce N;  
int main()  
{  
	printf("%d\n", a);  
	printf("%d\n", b);  
	return 0;  
}

2.C++输⼊&输出

形式:
输入:std::cout<<变量\常量\表达式\······<<endl;
输出:std::cin<<变量\常量\表达式\······<<endl;
注意点:

  • <iostream>是Input Output Stream的缩写,是标准的输⼊、输出流库,定义了标准的输⼊、输
    出对象。
  • std::cin是istream类的对象,它主要⾯向窄字符(narrow characters(of type char))的标准输⼊流。
  • std::cout 是iostream类的对象,它主要⾯向窄字符的标准输出流。
  • std::endl是⼀个函数,流插⼊输出时,相当于插⼊⼀个换⾏字符加刷新缓冲区
  • <<是流插⼊运算符,>>是流提取运算符。(C语⾔还⽤这两个运算符做位运算左移/右移)
  • C++的输⼊输出可以⾃动识别变量类型(本质是通过函数重载),C++的流能更好的⽀持⾃定义类型对象的输⼊输出。
  • ⼀般⽇常练习中我们可以using namespace std,实际项⽬开发中不建议using namespace std。
  • 没有包含<stdio.h>,也可以使⽤printf和scanf,在包含<iostream>间接包含了。vs系列
    编译器是这样的,其他编译器可能会报错。
  • 在io需求⽐较⾼的地⽅,如部分⼤量输⼊的竞赛题中,可以加上以下3⾏代码
#include<iostream>  
using namespace std;  
int main()  
{  
	// 可以提⾼C++IO效率  
	ios_base::sync_with_stdio(false);  
	cin.tie(nullptr);  
	cout.tie(nullptr);  
	return 0;  
}

3. 缺省参数

  • 缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。在调⽤该函数时,如果没有指定实参,则采⽤该形参的缺省值,否则使⽤指定的实参。
  • 缺省参数分为全缺省和半缺省参数,全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值。
  • 带缺省参数的函数调⽤,C++规定必须从左到右依次给实参,不能跳跃给实参
  • 函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值。

代码示例

//采用形参缺省值
#include <iostream>  
#include <assert.h>  
using namespace std;  

void Func(int a = 0)  
{  
	cout << a << endl;  
} 
int main()  
{  
	Func();// 没有传参时,使⽤参数的默认值  
	Func(10);// 传参时,使⽤指定的实参
	return 0;  
}

输出结果:

0
10
#include <iostream>  
using namespace std;  
// 全缺省  
void Func1(int a = 10, int b = 20, int c = 30)  
{  
	cout << "a = " << a << endl;  
	cout << "b = " << b << endl;  
	cout << "c = " << c << endl << endl;  
}   
//半缺省  
void Func2(int a, int b = 10, int c = 20)  
{  
	cout << "a = " << a << endl;  
	cout << "b = " << b << endl;  
	cout << "c = " << c << endl << endl;  
} 

//错误,必须依次从右往左缺省
//void Func2(int a=10, int b, int c)  
//{  
//	cout << "a = " << a << endl;  
//	cout << "b = " << b << endl;  
//	cout << "c = " << c << endl << endl;  
//} 

int main()  
{  
	Func1();  
	Func1(1);  
	Func1(1,2);  
	Func1(1,2,3);  
	Func2(100);  
	Func2(100, 200);
}

输出结果:

a = 10
b = 20
c = 30

a = 1
b = 20
c = 30

a = 1
b = 2
c = 30

a = 1
b = 2
c = 3

a = 100
b = 10
c = 20

a = 100
b = 200
c = 20

4. 函数重载

(1)定义

C++⽀持在同⼀作⽤域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者类型不同。这样C++函数调⽤就表现出了多态⾏为,使⽤更灵活。C语⾔是不⽀持同⼀作⽤域中出现同名函数的。

(2)函数重载的多种形式

  • 函数参数不同
  • 参数个数不同
  • 参数类型顺序不同
    注意:返回值不同不能作为重载条件,因为调⽤时也⽆法区分
#include<iostream>  
using namespace std;  
// 1、参数类型不同  
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;  
} 
// 2、参数个数不同  
void f()  
{  
	cout << "f()" << endl;  
}  
void f(int a)  
{  
	cout << "f(int a)" << endl;  
} 
// 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;  
}
void f1()  
{  
	cout << "f()" << endl;  
}
void f1(int a = 10)  
{  
	cout << "f(int a)" << endl;  
} 
int main()  
{  
	Add(10, 20);  
	Add(10.1, 20.2);  
	f();  
	f(10);  
	f(10, 'a');  
	f('a', 10);  
	return 0;  
}

运行结果:

int Add(int left, int right)
double Add(double left, double right)
f()
f(int a)
f(int a,char b)
f(char b, int a)

5.引用

(1)引用的概念和定义

引用不是新定义⼀个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间, 它和它引用的变量共用同⼀块内存空间。
在这里插入图片描述
如图所示,a和b有着同样的地址和值

类型& 引用别名=引用对象;

请添加图片描述

在这里面,a,b,c,d 共用一个地址空间

(2)引用的特性

  • 引用在定义时必须初始化请添加图片描述如图所示,不进行初始化就会发生报错

  • ⼀个变量可以有多个引用
    a,b,c 有着同样的值
    a,b,c 有着同样的值

在这里插入图片描述

当我想通过b=c,引用第二个实体时,发现b的地址仍然是a的地址,由此我们知道引用不能引用多个实体

(3)引用的使用

  • 引用传参和引用做返回值
    优点:1.不开空间,提高效率2.改变引用对象时同时改变被引用对象

引用传参

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;

void swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}
int main(){
int a = 5;
int b = 10;
swap(a, b);
cout << "a=" << a << "\n" << "b=" << b << endl;

}
输出结果为a=10,b=5
从此可得,swap函数通过引用传参可以直接通过改变形参来改变实参

值的临时变量做返回值

请添加图片描述
请添加图片描述

当我想直接对函数返回的只进行修改时出现报错,这是因为函数StackTop1函数返回了一个常性的临时变量,对它直接赋值会产生报错

引用做返回值

请添加图片描述请添加图片描述

但注意不要把临时变量传给引用,这样会产生空引用
请添加图片描述

当这个函数执行之后,top会被销毁,此时StackTop3就发生了空引用

(4)const引用

对于const引用只需要记住一句话——避免权限扩大
什么是权限放大,简单来说就是,每个变量有读和写两种权限,当引用有读和写两种权限,被引用的常量只有一种读的权限时就发生了权限扩大

int main(){
	//一般引用=常量
	const int a = 3;
	//int& ra = a;//权限放大出现报错
	const int& ra = a; 
	
	// 一般引用=临时变量
	
	//由表达式产生的临时变量
	int b = 3;
	int c = 4;
	//int& rb = a+b;//发生报错,a+b产生临时变量,只能读不能写
	const int& rb = a+b;
	
	//隐式转换产生的临时变量
	double d = 12.44;
	//int& rc = d//发生报错,d隐式类型转换产生临时变量,只能读不能写
	const int& rc =b;
}

const引用有以下几种用法:

  1. 权限平移
    常引用=常量
int main(){
	const int a = 3;
	const int& ra = a;
	
	int b = 3;
	int c = 4;
	const int& rb = b+c;
	
	double d = 12.44;
	const int& rc =b;
}

a本身只能读不能写,将其传给const引用ra后,ra也只能读取它们共有地址的值,而不能进行修改
b+c表达式会产生一个临时对象只能读不能写,只能传给const引用rb
d隐式类型转换产生临时对象,只能读不能写,只能传给const引用rc
注意:临时对象在被传给const引用后,寿命跟着引用走,不用担心临时对象销毁从而引用被销毁
2. 权限缩小
常引用=变量

int main(){
	int a = 3;
	const int rb = a;

}

a可读可写,将其传给const引用ra后,ra只能读取它们共有地址的值,不能进行修改,权限缩小

通过上面const引用的用法,我们可以发现,const引用的使用是为了防止有时候普通引用不适用的情况

请添加图片描述

如果我想调用这个函数,如果我不用const引用接收
那么我下面的写法就会产生报错

list a ;
a.push_back(1);
a.push_back(2);
a.push_back(3);

(5)指针和引用的关系

方面引用指针
语法上变量别名,不开空间存储一个变量地址,需要开空间
是否需要初始化必须初始化不一定要初始化
能否改变对象不能改变引用对象可改变指向对象
访问方式直接访问解引用后才能访问
安全性比较安全相对危险,容易产生野指针
sizeof大小引用类型的大小,因类型而改变空间一定,始终为地址空间所占的字节数

从汇编代码可以看出,在底层逻辑上,指针和引用有着相同的逻辑
请添加图片描述

6.inline

⽤inline修饰的函数叫做内联函数,编译时C++编译器会在调⽤的地⽅展开内联函数,这样调⽤内联 函数就需要建⽴栈帧了,就可以提⾼效率。内联函数存在的意义是代替C中的宏函数,因为宏函数的定义是在很繁琐。
我们将a+b定义成宏函数:

#define ADD(a,b) ((a)+(b))

以下是该宏函数定义的易错点:

//在末尾加分号
#define ADD(a,b) ((a)+(b));//cout<<ADD(a,b)<<endl;等效cout<<((a)+(b));<<endl;
//外面括号丢失
#define ADD(a,b) (a)+(b)//ADD(a,b)*5等效(a)*(b)*5
//里面括号丢失
#define ADD(a,b) (a+b)//ADD(x&y,x|y)等效为(x&y+x|y)而加的优先级大于&和|

从此看出宏函数十分麻烦易错,因此C++用内联函数来代替它

debug调试下默认不展开inline,可以通过以下方法来设置
请添加图片描述

请添加图片描述

将上面的宏函数转化为内联函数如代码所示,可以看出确实比宏函数明了很多

inline int ADD(int a,int b ) {
	return a + b;
}
int main() {
	int a = 1;
	int b = 2;
	int c = ADD(a , b);
}

汇编代码如下(暂时先不做解释,之后对汇编有较深理解了再补吧…)
请添加图片描述

但是当内联函数足够长和复杂时,编译器不会展开内联函数

inline int ADD(int a,int b ) {
	a++;
	a++
	b++
	b++
	return a + b;
}
int main() {
	int a = 1;
	int b = 2;
	int c = ADD(a , b);
}

请添加图片描述
注意:不建议将内联函数的定义和声明分别定义到两个文件中,直接放到.h文件中

7.nullptr

在学习NULL之前,让我们先回顾一下C语言中的NULL
在C语言中,NULL是一个宏

#ifndef NULL  
	#ifdef __cplusplus  
		#define NULL 0  
	#else  

	#define NULL  ((void *)0)
	#endif
#endif

从这个定义我们可以知道NULL有两种可能,一个是整型0,一个是空指针,就是因为有两种可能,会在可以进行函数重载的C++中遇到麻烦。
假设我们有一个函数fun该函数定义如下:

//C++
void fun(int x) {
	x++;
	cout << x;
}
void fun(int* x) {
	if (x == NULL) {
		cout << "x为空指针";
	}
}
int main() {
	fun((void*)NULL);//发生报错
}
//C
void fun(int* x) {
	if (x == NULL)
		printf("x为空指针");
}
int main() {
	fun((void*)NULL);//正常运行(不用显式转换也行)	
}

当我们用fun(NULL)调用fun时,编译器并不知道我们想调用的是第一个fun还是第二个fun,而产生错误调用,这时你可能会说用fun(int* x),但因为C++的隐式转换比C更加严格,对于指针类型,C++需要显式的类型转换才能从void*转换为其他具体类型的指针,此时 void* 并不能隐式转换为int* 从而调用第二个函数。

说了这么多,只需要得出一个结论即可:NULL在能重载的C++中并不适用,需要我们使用nullptr

C++11中引⼊nullptr,nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字⾯量,它可以转换
成任意其他类型的指针类型。使⽤nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被
隐式地转换为指针类型,⽽不能被转换为整数类型

void fun(int x) {
	x++;
	cout << x;
}
void fun(int* x) {
	if (x == NULL) {
		cout << "x为空指针";
	}
}
int main() {
	fun(nullptr);//正常输出"x为空指针"
	return 0;
}
  • 25
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值