一篇文章带你初识C++

Posted  落禅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一篇文章带你初识C++相关的知识,希望对你有一定的参考价值。

1.命名空间

关键字:namespace

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

命名空间的定义

定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名 空间的成员。

//1. 普通的命名空间
namespace N1 // N1为命名空间的名称
{
 // 命名空间中的内容,既可以定义变量,也可以定义函数
 	int a;
	 int Add(int left, int right)
 	{
		 return left + right;
 	}
}
//2. 命名空间可以嵌套
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;
 	}
 	
}
//3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
namespace N1
{
 	int Mul(int left, int right)
  	{
 		return left * right;
 	}
}

命名空间的使用

命名空间的使用有三种方式:
加命名空间名称及作用域限定符
使用using将命名空间中成员引入
使用using namespace

//1.使用命名空间名称及作用域限定符
int main()
{
	 printf("%d\\n", N::a);
 	 return 0; 
}
//2.使用using将命名空间中成员引入
using N::b;
int main()
{
 	printf("%d\\n", N::a);
 	printf("%d\\n", b);
 	return 0; 
}
//3.使用using namespace 
using namespce N;
int main()
{ 
	printf("%d\\n", N::a); 
	printf("%d\\n", b); 
	Add(10, 20);
    return 0;
 }

在C++中我们开头总是写using namespace std; 很多人压根不知道这句话的意思,这里的std就是命名空间,当然这句话的意思就是使用标准命名空间

2.函数缺省

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

缺省参数分类:
全缺省参数
半缺省参数

//1.全缺省参数
void TestFunc(int a = 10, int b = 20, int c = 30)
{
 	cout<<"a = "<<a<<endl;
	cout<<"b = "<<b<<endl;
	cout<<"c = "<<c<<endl;
}
//2.半缺省参数
void TestFunc(int a, int b = 10, int c = 20)
{
	 cout<<"a = "<<a<<endl;
	 cout<<"b = "<<b<<endl;
 	cout<<"c = "<<c<<endl;
 }

注意:

  1. 半缺省参数必须从右往左依次来给出,不能间隔着给
  2. 缺省参数不能在函数声明和定义中同时出现
  3. 缺省值必须是常量或者全局变量
  4. C语言不支持(编译器不支持)

3 .函数重载

函数重载:即在C++中容许有同名的函数 函数重载的条件:函数的参数类型不同,返回值不同 注:函数的返回值不能构成函数重载的条件

#include<iostream>
using namespace std;
void Print(int a)
{
	cout << a << endl;
}
void Print(double a)
{
	cout << a << endl;
}
int main()
{
	Print(1.0);
	Print(1);
	return 0;
}

如上述例子所述,我们要实现两个自定义打印函数,一个打印整数,一个打印浮点数,两个函数列表的参数类型不同
而函数名却相同,但是我们在编译时却不会报错,这就是函数重载,让我们实现了打印两个不同类型打印函数
而在C语言中我们就要起两个函数名来实现这件事,如Print_int,Print_double
那么为什么会出现函数重载这件事呢,这就要从编译链接的过程开始说起:
1.预处理:头文件的展开+宏替换+去掉注释+条件编译
2.编译:检查语法,将代码转化成汇编代码
3.汇编:汇编代码转化为二进制的机器码
4.链接:将所有文件连接在一起,生成可执行程序 而C++在编译过程中有函数名修饰规则:函数的返回值+函数名+函数参数类型,这样每个函数就能够进行独立识别,既不会产生命名冲突,如下所示:


#include<iostream>
void  print(int a)
{
	cout << a << endl;
}
void print(string str)
{
	cout << str << endl;
}
using namespace std;
int main()
{
	print(1);
	print("hello  world");
}

将上述代码在Linux下利用g++编译之后,我们会产生下述结果:

Print(int a)

这里我们会发现void print(int a)在这里变成了_Z5printi

void Print(string str)

而void print(string str)在这里变成了_Z5printSs
我们来对比一下:
从void print(inta)—>_Z5printi;
而void printf(string str)—>_Z5printSs;
前面_Z5我们也许不认识,但是看到print我们应该能够联想到这是我们定义的函数名,那么后面的i我们也就可以去联想到知识函数的参数类型,i代表int,Ss代表string,那么我们就可以大胆的猜测_Z5就是前面的函数的返回值类型,这样每一个函数在编译之后:就会形成:
函数返回值+函数名+参数列表类型
这将成为为函数唯一修饰,这样看似两个相同的函数就会被特定的区别开来,也就出现了函数重载现象
而在C语言中,我们将下面代码在Linux gcc下编译之后:

#include<iostream>
using namespace std;
int Ad(int a, int b)
{
	return a + b;
}
void func(int a,  double b, int* p)
{

}
int main()
{

	return 0;
}

在这里插入图片描述

并没有出现像上面C++中那样有函数名修饰规则,而是直接就写了函数名,这样要是出现两个相同的函数名他就无法进行区分,则会报错,这也就解释了为什么C++支持函数重载,而C语言不支持函数重载,总而言之就是二者的函数名修饰规则不同

4.引用

简单来说就是给一个变量起别名 引用不是重新写一个变量,而是给一个变量起别名,他和他引用的变量共同访问一块内存空间
基本语法:数据类型 &别名 = 原名
例如 int a=10;
int &b=a;

在这里插入图片描述

如上图所示,我们给变量a起别名为b,此时变量a和b就能共同访问这块内存空间,此时我们改变b的值为100,我们会发现a的值也会变为100,这就是最基础的引用
.引用的注意事项: 引用必须初始化 引用在初始化后不可以改变

引用的作用

1.引用做函数参数
作用:函数传参时,可以利用引用的技术让形参修饰实参
实例:

#include<iostream>
using namespace std;
//交换函数
//1.址传递
void swap1(int a, int b)
{
	int temp = a;
	a = b;
	b = temp;
	cout << "swap1 a=" << a << endl;
	cout << "swap2 b=" << b << endl;
}
//2.地址传递
void swap2(int* a, int* b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
}
//3.引用传递
void swap3(int& a, int& b)
{
	int temp = a;
	a = b;
	b = temp;
}
int main()
{
	int a = 10;
	int b = 20;
	//swap1(a, b);//值传递形参不会改变实参
	//swap2(&a, &b);//地址传递形参会改变实参
	swap3(a, b);//引用传递形参也会修饰实参
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
	system("pause");
	return 0;
}
总结:引用的效果和地址传递效果是一样的,引用语法更加清楚简单

2.引用做函数返回值
作用:引用是可以做函数返回值存在的
注意:不要返回局部变量引用
实例:

#include<iostream>
using namespace std;
//引用做函数返回值
//1.不要返回局部变量的引用
int& test01()
{
	int a = 10;//局部变量在栈区中
	return a;
}
//2.函数的调用可以作为左值
int& test02()
{
	static int a = 10;//全局变量程序运行完后由系统自动释放
	return a;
}
int main()
{
	int &ret=test01();
	cout << ret << endl;//第一次正确,编译器做了保留,
	cout << ret << endl;//第二次错误,因为a的内存已经释放
	int& ref = test02();
	cout << "ref=" << ref << endl;
	cout << "ref=" << ref << endl;
	test02() = 1000;///如果函数返回值是引用,这个函数调用可以作为左值
	cout << "ref=" << ref << endl;
	system("pause");
	return 0;
}

引用的本质

本质:引用的本质在c++内部实现是一个常量指针
int&a=b==>int* contst a=&b;
指针指向不可以修改,但是其值可以修改
PS:引用一旦初始化就不可以发生变化

#include<iostream>
using namespace std;
void test(int& a)
{
	a = 100;
}
int main()
{
	int a = 10;
	int& b = a;//相当于int* const b=&a;
	b = 20;//相当于*b=20
	cout << "a=" << a<<endl;
	cout << "b=" << b << endl;
	test(a);
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
	system("pause");
	return 0;
}

引用与指针的区别

1.引用必须进行初始化,而指针不用进行初始化
2.引用自增增加的是源数据本身,而指针自增为增加源数据大小的步长
3.指针可以为空,引用不可以为空
4.指针和引用的大小不同,指针的大小只与操作系统有关,32为系统下为4字节,64位系统下位8字节,而引用的大小为源数据的大小
5.有多级指针,但是没有多级引用
6.访问实体方式不同,指针需要解引用,而引用编译器自动处理
7.引用在初始化一个实体之后就不能再引用其他实体,而指针可以指向任意同类型实体
8.引用比指针更加安全

5.内联函数

内联函数:以inline修饰的函数叫做内联函数 编译时会在调用内联函数的地方展开没有压栈的开销,可以提高函数运行效率
这为一些反复调用反复调用的小函数提供了方便 比如我们在排序时经常写交换函数,而在加入inline之后,没有了压栈的开销,可以提高程序的效率

#include<iostream>
using namespace std;
inline void Swap(int &a, int& b)
{
	int c = a;
	a = b;
	b = c;
}
int main()
{
	//使用Swap函数
	int a = 1;
	int b = 2;
	Swap(a,b);
	return 0;
}

关键字:inline

而在C语言中为了减少压栈的开销可以使用宏函数,在预处理阶段进行替换,不需要压栈的开销,那么C++兼容C,为什么C++还要推荐使用内联函数呢,这就不得不说到宏函数的缺点:
1.使用宏函数不支持调试
2.宏语法复杂,容易出错,代码可读性差,维护性差
3.宏常量没有类型

C++中那些技术替代了宏函数
1.常量定义利用const修饰
2.函数定义为内联函数

不是内联函数
内联函数

以上·就是内联函数与普通函数效率的对比

那么内联函数这么浩,要不要每个函数都加inline函数使之变成内联函数呢,答案是否定的
1.inline是一种以空间换时间的做法,省去调用函数数额开销,所以代码过长 或者有循环/递归时不推荐使用内联函数
2.inline对编译器来说只是一个建议,编译器会自动进行优化,如果函数体内代码过长或者使用循环/递归 编译器会自动忽略内联
3.inline不建议声明与定义分开,分离会导致链接错误,因为inline被展开 就没有函数地址了,链接就会出现错位找不到,例如我们不能在.h文件中声明内联函数,在.cpp中定义内联函数,这样会报错

6.auto关键字(C++11)

在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但遗憾的是一直没有 人去使用它,大家可思考下为什么?
C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型
指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得

auto使用注意事项

1.必须进行初始化
2.一行可以定义多个变量,但是一行定义的多个变量类型要一样
3.auto在声明指针时与auto*没有区别
4.auto在使用引用类型时必须加&
5.auto不可以用来定义数组
6.auto不可以当作函数形参来使用
7. auto在实际中最常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有lambda表达式等 进行配合使用

#include<iostream>
using namespace std;
int main()
{
	int a = 10;
	auto b = a;//根据10的值自动推导出b的值为int类型
	int* p = &a;
	auto ptr = p;
	auto* ptr1 = p;
	cout <<"a的类型是" <<typeid(a).name() << endl;
	cout << "b的类型是" << typeid(b).name() << endl;
	cout << "p的类型是" << typeid(p).name() << endl;
	cout << "ptr的类型是" << typeid(ptr).name() << endl;
	cout << "ptr1的类型是" << typeid(ptr1).name() << endl;

	return 0;
}

auto关键字用法

基于范围的for循环:

语法:auto 变量:数组名
意义:将数组中每一个元素的值循环赋值给我们创建的auto变量
注意我们要是想改变数组中的内容就必须加&才能改变数组中的内容

#include<iostream>
using namespace std;
int main()
{
	int arr[] = { 1,2,3,4,5 };
	for (auto e : arr)
	{
		cout << e << " " << endl;
	}
}
//如上代码所示,以前我们在遍历数组时需要利用基本的for循环来遍历数组,而在这里我们就可以不使用for循环来直接遍历数组

在这里插入图片描述

7. 指针空值nullptr(C++11)

在C98中空指针为NULL,它底层是
#define NULL 0
这出现了很多问题
例;

#include<iostream>
using namespace std;
void Print(int a)

{
	cout << "int"<<endl;
}
void Print(int* p)
{
	cout << "int*" << endl;
}
int main()

{
	Print(0);
	Print(NULL);
	return 0;
}

在这里插入图片描述

我们想让Print(0)打印int,而Print(NULL)打印int*,然而都打印了int,而在C11中为了解决这个问题,空指针被重新定义为nullptr
底层为:(void*)0;

在这里插入图片描述

以上就是本篇文章所有内容,希望各位看官能够喜欢,如果有错误的地方,还请多多斧正,感谢大家支持

以上是关于一篇文章带你初识C++的主要内容,如果未能解决你的问题,请参考以下文章

从0带你入门C++,本文3万字含C++全套基础语法和练习套题,肝!

C++继承不会怎么办?一篇文章带你搞懂C++继承!!!

C++丨初识C++像极了C语言

带你初识Angular中MVC模型

初识Mysql 连接器的收获(包含JDBC API最新文档)以及一些c++的有用技巧

C++基础入门丨1. 初识C++像极了C语言