C++入门

c++入门

C++入门知识主要是解决C语言的一些不足之处,我们下面直接步入正题。

我们下面来学习c++的第一个程序:

举例:`

cpp 复制代码
#include <iostream>
//std是所有c++库命名空间
using namespace std;//放到全局

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

我们学习这个程序之前要先知道一个知识点:命名空间域

命名空间

我们在使用C语言的时候,通常会遇到变量定义冲突的情况,而C++中的命名空间域就是来解决这个问题的,下面我们来学习这个知识点:

域的概念

首先,域有以下几个分类:

1.全局域

2.局部域

3.命名空间域

4.类域

其中全局域和局部域都会影响生命周期和访问,而命名空间域只影响访问。

而类域这里追秋还没有学到,所以这里就不给大家激讲解了。

下面我们写一个域的程序要让我们更快的了解域的概念:

cpp 复制代码
namespace hhd1
{
	int x = 0;

}

namespace hhd2
{
	int x = 1;
}

int main()
{
	printf("%d\n", hhd1::x);
	printf("%d\n", hhd2::x);

	return 0;
}

其中namespace就是域的意思,hhd1和hhd2就是这个域的名字,我们在使用指定域中的变量的时候是用::符号来使用,::就是域作用限定符,作用是指定编译器去访问哪一个域。

而在定义域的时候进行名字的定义,那么势必会出现名字冲突的情况:
当命名空间域名字相同时,编译器会将其中的内容合并到一起。

接下来我们看下一个程序:

cpp 复制代码
namespace hhd1
{
	int x = 111;
}

using namespace hhd1;

int main()
{
	printf("%d\n", x);

	return 0;
}

讲解:using namespace hhd1 就是将命名空间域hhd1展开,使编译器可以访问到该空间中,

注:在命名空间中定义的变量依旧属于全局变量,展开后也是,编译器依旧是先访问局部然后全局,只是展开后编译器有权利去该空间域中搜索。

我们在命名空间域中还可以定义一些常见的量,比如:函数,结构体。

cpp 复制代码
namespace hhd1
{
	int x = 0;

	int Add(int left, int right)
	{
		return left + right;
	}

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

int main()
{
	printf("%d\n",hhd1::x);
	
	//定义结构体指针
	struct hhd1::Node phead;
	
	return 0;
}

其中我们看到函数、结构体也是可以在命名空间域中定义的,其中在主函数中还定义了结构体指针。

命名空间域的嵌套

举例:

cpp 复制代码
#include <iostream>

namespace hhd
{
	namespace sz
	{
		int x = 111;
	}

	namespace ls
	{
		int x = 120;
	}
}

int main()
{
	printf("%d\n", hhd::sz::x);
	printf("%d\n", hhd::ls::x);
	return 0;
}

我们在使用命名空间域的时候可以对域进行嵌套定义,这样就可以防止在同一域中的变量定义冲突的问题,当然,这个嵌套是可以多次嵌套的,只要你能把这个逻辑理清楚的话。

域的使用方式

1.不展开,在指定域搜索

cpp 复制代码
#include <iostream>
namespace hhd
{
	int x = 120;
}
int main()
{
	printf("%d\n", hhd::x);
	return 0;
}

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

cpp 复制代码
using N::b;
int main()
{
	printf("%d\n", N::a);
	printf("%d\n", b);
	return 0;
}

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

cpp 复制代码
using namespce N;
int main()
{
	printf("%d\n", N::a);
	printf("%d\n", b);
	Add(10, 20);
	return 0;
}

C++输入输出

输出cout

使用方法:

cpp 复制代码
int main()
{
	cout << "xxxxx" << endl;
	return 0;
}

其中操作符<<有两种含义在C++中,

1.左移,就是我们C语言阶段所学习的关于二进制位的移动。

2.流插入,且自动识别类型

举例:

cpp 复制代码
int main()
{
	int x = 0;
	double y = 1.2;
	cout << x<< y << endl;

	return 0;
}

在对cout语句使用的时候不需要像printf中一样注明类型,就可以直接打印在显示器上。

输入cin

举例:

cpp 复制代码
int main()
{
	int x = 0;
	double y = 1.2;
	cin >> x >> y ;
	cout << x << y << endl;
	return 0;
}

其中>>操作符在这里也是有两种作用,

1.右移,对操作数的二进制位进行操作

2.流提取,自动识别类型,不需要单独注明操作数的类型。

举例:

cpp 复制代码

缺省参数

什么是缺省参数?

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

举例:

cpp 复制代码
#include <iostream>

using namespace std;

void func(int i = 0)
{
	cout << i << endl;
}
int main()
{
	func(1);
	func();
	return 0;
}

运行结果:

缺省参数有以下几个分类:

全缺省

cpp 复制代码
void func(int x = 10, int y = 20, int z = 30)
{
	cout << "x="<< x << endl;
	cout << "y="<< y << endl;
	cout << "z="<< z << endl<<endl;
}

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

运行结果:

半缺省(部分缺省)

cpp 复制代码
void func(int x, int y = 20, int z = 30)
{
	cout << "x="<< x << endl;
	cout << "y="<< y << endl;
	cout << "z="<< z << endl<<endl;
}

int main()
{
	func(1, 2, 3);
	func(1, 2);
	func(1);

	return 0;
}

运行结果:

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

函数重载

自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重

载了。比如:以前有一个笑话,国有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个是男足。前者是"谁也赢不了!",后者是"谁也赢不了!"

函数重载概念

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

函数重载有以下几个分类:

参数类型不同
cpp 复制代码
// 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;
}

函数名相同,但是参数的类型不同。

注:要是参数相同但是返回值类型不同也是不构成重载的,因为编译器依旧无法区分。

参数个数不同
cpp 复制代码
// 2、参数个数不同
void f()
{
	cout << "f()" << endl;
}
void f(int a)
{
	cout << "f(int a)" << endl;
}

函数名相同,但是函数的参数数量不同。

参数顺序不同
cpp 复制代码
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语言所没有的。这个点之后的文章追秋会重点讲解!

引用

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

一个变量可以有多个别名

举例:

cpp 复制代码
int main()
{
	int a = 0;
	//引用,b就是a的别名
	int& b = a;

	cout << &b << endl;
	cout << &a << endl;

	//可以取多次别名
	int& c = a;
	//也可以对别名取别名
	int& d = b;

	cout << &c << endl;
	cout << &d << endl;

	return 0;
}

运行结果:

下面来举一个例子来引出引用在函数方面的使用:

cpp 复制代码
void Swap(int& c, int& d)
{
	int tmp = c;
	c = d;
	d = tmp;
}

int main()
{
	int a = 1;
	int b = 5;

	Swap(a, b);
	cout << a << endl << b << endl;
	return 0;
}

运行结果:

此时ab中的值已经被交换,那么这个时候就有人要问了,在函数形参取名的时候可以和实参一样吗?

解答:可以!

引用在定义时必须初始化

cpp 复制代码
int main()
{
	int a = 0;
	int& b = a;

	int& c;
	return 0;
}

我们可以看到上述代码在编译的时候报错。

为什么引用必须初始化?

引用如果不初始化的话,那么回合赋值产生歧义。

cpp 复制代码
int main()
{
	int a = 0;
	int b = 10;

	int d;
	int& c;
	c = b;
	d = b;
	return 0;
}

上述代码c的引用和d的赋值部分根本分不清楚。

引用一旦引用一个实体,再不能引用其他实体

引用一旦确定引用一个实体之后,就不能在引用另一个实体了。

引用无法改变指向,这也是引用无法完全代替指针的一个重要的方面。

下面举一个具体场景:

在链表部分我们通常需要改变指针的指向让指针向前或向后移动,从而达到我们想要的效果,但是引用无法改变指向,因此不能够代替指针。

那么问题来了,Java、python等语言没有指针如何实现链表?

答:他们的引用可以改变指向哈哈哈

cpp 复制代码
int main()
{
	int a = 0;
	int b = 10;

	int& c = a;
	&c = b;
	return 0;
}

下面说几个引用在函数传参的应用:

cpp 复制代码
void PushBack(struct Node*& phead, int x)
{
	phead = newnode;
}
int main()
{
	struct Node* plist = NULL;

	return 0;
}

此处就是函数中的phead就是struct Node* plist的别名,也就是phead等价于plist;

使用场景

参数
cpp 复制代码
void Swap(int& left, int& right)
{
	int temp = left;
	left = right;
	right = temp;
}

1.做输出型参数;

2.对象比较大,减少拷贝,提高效率。

这两个效果,指针都可以做,只是引用更方便。

返回值

场景一:

cpp 复制代码
int func()
{
	int a = 1;
	return a;
}
int main()
{
	int ret = func();
	cout << ret << endl;
	return 0;
}

场景二:

cpp 复制代码
int& func()
{
	int a = 1;
	return a;
}
int main()
{
	int ret = func();
	cout << ret << endl;

	return 0;
}

场景三:

cpp 复制代码
int& func()
{
	int a = 1;
	return a;
}
int main()
{
	int& ret = func();
	cout << ret << endl;

	return 0;
}

此时在这个场景下做进一步的改动:

cpp 复制代码
int& func()
{
	int a = 1;
	return a;
}
int& fun()
{
	int b = 6;
	return b;
}
int main()
{
	int& ret = func();
	cout << ret << endl;

	fun();
	cout << ret << endl;

	return 0;
}

运行结果:

在以上这种场景中,只是简单调用了另一个函数,其实啥都没做,这里就会出现随机值。

结论:返回值出了函数作用域声明周期就结束了,就会被销毁,不能做引用返回。

而全局变量、静态变量、堆上空间(malloc)就可以用引用返回。

指针和引用的区别

语法:
1.引用是别名,不开空间,指针是地址,需要开空间;
2.引用必须初始化,指针可以初始化也可以不初始化;
3.引用不能改变指向,指针可以;
4.引用相对安全,没有空引用,但是有空指针,容易出现野指针,但是不容易出现也引用;

5.在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)

  1. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

  2. 有多级指针,但是没有多级引用

  3. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理

  4. 引用比指针使用起来相对更安全
    底层:
    在汇编层面上,没有引用,都是指针,引用编译后也会被转换成指针。

内联函数

以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。

内联函数其实和宏以及函数有关系,下面我们来回顾以下宏的一些知识点:

cpp 复制代码
#define ADD(a,b) ((a) + (b)) 

int main()
{
	int a = 1;
	int b = 2;

	int c = ADD(a, b);
	cout << c << endl;
	return 0;
}

以上代码就是宏的使用的一个简单代码,其中也会出现几个问题:为什么a、b要单独加括号将他们单独放在一起:

解答:因为可能原始参数可能传的不仅仅是一个参数,而是一个表达式,下面我们来看:

cpp 复制代码
#define ADD(a,b) ((a) + (b)) 

int main()
{
	int a = 1;
	int b = 2;

	int c = ADD(a + b , a - b);
	cout << c << endl;
	return 0;
}

上面这个例子就很好的说明了宏的一个正确使用方式。相比较于函数,宏是不需要建立栈帧的,也就减少了栈帧空间的开销,当然宏也不是完美的,宏也有缺点:

宏的缺点:

1、语法复杂,坑很多,不容易控制;

2、不能调试;

3、没有类型安全的检查。

而内联函数就将函数和宏的优势结合在了一起:

我们知道:函数在传递参数的时候,如果参数是表达式,那么会将表达式的值算好在传值,而宏是不需要单独建立栈帧,编译器在编译的时候会自动将宏的内容进行替换,这里内联函数就将这一点完美的应用到了:

cpp 复制代码
inline int ADD(int x, int y)
{
	return x + y;
}
int main()
{
	int a = 1;
	int b = 2;

	int c = ADD(a + b , a - b);
	cout << c << endl;
	return 0;
}

这里的运算不建立栈帧,直接在原地进行替换且参数是运算好的。

内联函数特性

  1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。
  2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。
  3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。下面将一个例子来说明这个问题:

下面将一个内联函数一个在实践当中的一个小应用的地方,

当一个函数的声明和定义没有分离的时候,函数在运行的时候会出现报错:

想要解决这个问题我们有以下几个解决方法:

1.声明和定义分离。

2.static修饰函数

3.inline修饰

用inline修饰的函数直接在.h文件中定义,作用机制和static类似,因为inline修饰对象较小时,编译时会在使用的地方展开,因此不会再符号表中出现,也不会出现链接错误的问题。当然,目标函数不能过大,不然inline会不起作用的。

auto关键字

识别类型

cpp 复制代码
int main()
{
	int aw = Add(1, 2);

	int a = 1;

	auto b = a;
	auto c = &a;
	auto* d = &a;
	
	return 0;
}

在上述的情况下,定义一个新的数据就不用特意声明他的类型,系统会自动识别数据类型并完成定义。

auto关键字也可以定义函数类型:

当然在这里自动识别类型定义显得价值不大,但是在以后类型很复杂的情况下,auto函数的价值就会显示出来。

auto不能推导的场景

不能作函数的参数

编译器允许auto作为返回类型但不能作为参数。

不能用来声明数组类型
cpp 复制代码
void TestAuto()
{
	int a[] = {1,2,3};
	auto b[] = {4,5,6};
}
  1. 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法
  2. auto在实际中最常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有lambda表达式等进行配合使用。

基于范围的for循环(C++11)

范围for的语法

cpp 复制代码
int main()
{
	int a[] = { 1,2,3,4,5 };
	for (auto e : a)
	{
		cout << e << endl;
	}
	return 0;
}

这其实就是一种新的for循环的使用方式,auto会将数组a中的值依次放到e中,然后我们打印的结果就是:

范围for的使用条件

1. for循环迭代的范围必须是确定的
对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。
注意:以下代码就有问题,因为for的范围不确定
2. 迭代的对象要实现++和==的操作。(关于迭代器这个问题,以后会讲,现在提一下,没办法讲清楚,现在大家了解一下就可以了)

指针空值nullptr(C++11)

这个点其实是在弥补C++之前的缺陷:

cpp 复制代码
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,比如:

cpp 复制代码
void f(int)
{
	cout<<"f(int)"<<endl;
}
void f(int*)
{
	cout<<"f(int*)"<<endl;
}
int main()
{
	f(0);
	f(NULL);
	f((int*)NULL);
	return 0;
}

程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。

在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void*)0。

注意:
*1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
2. 在C++11中,sizeof(nullptr) 与 sizeof((void
)0)所占的字节数相同。
3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。**

相关推荐
娅娅梨20 分钟前
C++ 错题本--not found for architecture x86_64 问题
开发语言·c++
兵哥工控24 分钟前
MFC工控项目实例二十九主对话框调用子对话框设定参数值
c++·mfc
汤米粥25 分钟前
小皮PHP连接数据库提示could not find driver
开发语言·php
冰淇淋烤布蕾28 分钟前
EasyExcel使用
java·开发语言·excel
我爱工作&工作love我32 分钟前
1435:【例题3】曲线 一本通 代替三分
c++·算法
拾荒的小海螺35 分钟前
JAVA:探索 EasyExcel 的技术指南
java·开发语言
马剑威(威哥爱编程)1 小时前
哇喔!20种单例模式的实现与变异总结
java·开发语言·单例模式
娃娃丢没有坏心思1 小时前
C++20 概念与约束(2)—— 初识概念与约束
c语言·c++·现代c++
lexusv8ls600h1 小时前
探索 C++20:C++ 的新纪元
c++·c++20
lexusv8ls600h1 小时前
C++20 中最优雅的那个小特性 - Ranges
c++·c++20