【C++】C++入门

目录

一. C++关键字

二. 命名空间 - namespace

1. 标准写法

2. 特性

2. 注意事项

三. C++ 输入 & 输出

四. 缺省参数

应用场景

五. 函数重载

C++支持函数重载的原理 - 名字修饰(name Mangling)

六. 引用

1. 使用场景

1.1 做参数

1.2 做返回值

1.3 总结:

1.4 应用场景

2. 常引用问题

3. 引用和指针的区别

七. 内联函数

注意:

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

1. auto 使用细则

2. auto 不能推导的场景

九. 范围 for(C++ 11)

1. 语法

2. 使用条件

十. 空指针 nullptr(C++ 11)


整个 C++ 入门都是在补充 C 语言的不足

一. C++关键字

二. 命名空间 - namespace

C语言中,全局命名冲突:1. 我们跟库冲突 2. 我们互相之间的冲突

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

int rand = 0;

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

这段代码,删掉第二行可以编译通过。加上第二行无法编译通过:“rand”: 重定义;以前的定义是“函数”
这里是和库里生成随机数的 rand 冲突了

包含#include <stdlib.h>,在预处理阶段拷贝。==> 全局已经有了 rand 想定义 rand,要定义在域里

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

namespace bit
{
	int rand = 1;
}				// 没有 ;

int main()
{
	printf("%d\n", bit::rand);
	printf("%p\n", rand);

	return 0;
}

        C语言无法解决这个问题,C++的命名空间可以


namespace 是关键字,定义一个域。想让上面的程序不冲突,可以用域进行隔离

类域、命名空间域、局部域、全局域

int a = 0;

int main()
{
	int a = 1;

	printf("%d\n", a); // 1 局部优先
	return 0;
}

同一个域里不能定义同一个变量。这两个 a 可以同时存在

// 访问全局的 a
int a = 0;

int main()
{
	int a = 1;

	printf("%d\n", a); // 1

	// ::域作用限定符,在 ::左边的域访问。左边是空白,代表去全局域访问
	printf("%d\n", ::a); // 0

	return 0;
}

1. 标准写法

3个 a 在不同域,可同时存在

当这几个域同时存在时,变量搜索的范围优先级:局部域 > 全局域 > 展开了命名空间域 or 指定访问命名空间域

展开:编译时是否去命名空间中搜索

namespace bit // 定义一个域叫 bit
{
	int a = 1; // 把变量放进域里面
}

using namespace bit; // 展开了命名空间

int main()
{
	printf("%d\n", a); // 1
	return 0;
}
// 3个 a 全访问
int a = 0;

namespace bit
{
	int a = 1;
}

int main()
{
	int a = 2;

	printf("%d\n", a); // 2
	printf("%d\n", ::a); // 0
	printf("%d\n", bit::a); // 1 指定访问命名空间域

	return 0;
}
int a = 0;

namespace bit
{
	int a = 1;
}

using namespace bit; // 展开了命名空间

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

	return 0;
}

展开 ==> 暴露在全局。
此时这俩 a 不能同时存在        

命名空间的意义是防止和别人冲突,我把自己围起来,指定访问
展开就又冲突了,不要轻易的用 using namespace bit ; 这句话

2. 特性

命名空间里可以定义:变量、函数、类型(结构体)、嵌套命名空间

namespace N1
{
	int a = 0;
    int b;
	int Add(int left, int right)
	{
		return left + right;
	}

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

	namespace N2
	{
        int a = 1; // 俩 a 不是同一个
		int c;

		int Sub(int left, int right)
		{
			return left - right;
		}
	}
}

int main()
{
	printf("%d\n", N1::a);
	printf("%d\n", N1::N2::a);
    printf("%d\n", N1::Add(1, 2));

	return 0;
}

同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。

// Queue.h
#pragma once
namespace bit
{
	int y = 1;
}

// Stack.h
#pragma once
namespace bit
{
	int x = 0;
}

// Test.cpp
#include "Stack.h"
#include "Queue.h"

namespace bit
{
	int rand = 2;
}

int main()
{
    printf("%d\n, bit::x); // 0
    printf("%d\n, bit::y); // 1
    printf("%d\n, bit::rand); // 2
}

同一个命名空间如果冲突,比如:

// Queue.h
#pragma once
namespace bit
{
	int x = 3;
}

被合并到同一个命名空间,不能有同名变量,报错

怎么解决? 嵌套命名空间

2. 注意事项

#include <iostream>
#include <vector>
#include <list>

int main()
{
	cout << "hello world" << endl; // 报错
    list<int> lt; // 报错
    
	return 0;
}

编不过。C++标准库(iostream, cout就是标准库里的东西)、STL都在 std 里面

包含了3个头文件,在预处理阶段会展开。会把3个头文件里定义的库的东西都放出来。库里面的东西又都是分在 std这个命名空间域 里的

包含了头文件,eg:这里定义 list

#include <iostream>
#include <vector>
#include <list>

namespace std
{
	class list
	{	};
}

int main()
{
	cout << "hello world" << endl; // 报错
	list<int> lt; // 报错

	return 0;
}

默认不会去命名空间里面搜索

正确写法:

#include <iostream>
#include <vector>
#include <list>

int main()
{
	std::cout << "hello world" << std::endl;
	std::cout << "hello world" << std::endl;
	std::cout << "hello world" << std::endl;
	std::cout << "hello world" << std::endl;
	std::list<int> lt;

	return 0;
}

这样每次都加 std:: 很烦?        一把展开

#include <iostream>
#include <vector>
#include <list>

using namespace std;

int main()
{
	cout << "hello world" << endl;
	std::cout << "hello world" << std::endl; // 加不加都可以
	list<int> lt;

	return 0;
}

直接展开会有风险,我们定义如果跟库重名,就报错。
项目里不要展开,项目里推荐指定访问

展开某个:把常用的展开

#include <iostream>
#include <vector>
#include <list>

using std::cout;
using std::endl;

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

	std::list<int> lt;

	return 0;
}

三. C++ 输入 & 输出

" hello world ":IO流         " hello world " 流向 cout

#include <iostream>

using std::cout;
using std::endl;

int main()
{
	int x = 10;
	cout << "hello world" << x << '\n' << endl;
	cout << "hello world" << endl;

	return 0;
}

相比 printf,cout 可一行连续插入多个变量、常量; printf 要指定类型,cout 自动识别类型

int main()
{
	int x = 10;
	double d = 11.11;
	cout << x << " " << d << endl;

	std::cin >> x >> d;
	cout << x << " " << d << endl;
    printf("%d %.5f\n", x, d);
    
	return 0;
}

自动识别,精度丢失。想控制精度很难。喜欢用 C语言 控制精度

四. 缺省参数

void Func(int a = 0)
{
	cout << a << endl;
}
int main()
{
	Func(); // 0
	Func(10);// 10

	return 0;
}

没有传参时,使用参数的默认值                传参时,使用指定的实参

注意:1. 函数传参,从左往右接收
           2. 半缺省,必须从右往左缺省。且此时必须传参。不能指定传参
           3. 缺省参数不能在函数声明和定义中同时出现。声明中给缺省参数,定义中不给


全缺省:

void Func(int a = 10, int b = 20, int c = 30)
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl;
}

int main()
{
	Func(); // 10 20 30
	Func(1); // 1 20 30
	Func(1, 2); // 1 2 30
	Func(1, 2, 3); // 1 2 3

	return 0;
}

半缺省 - 必须从右往左缺省:

void Func(int a, int b = 20, int c = 30)
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl;
}

int main()
{
	// Func(); 错误,此时必须传参
	Func(1); // 1 20 30
	Func(1, 2); // 1 2 30
	Func(1, 2, 3); // 1 2 3

	return 0;
}

应用场景

struct Stack
{
	int* a;
	int top;
	int capacity;
};

void StackInit(struct Stack* pst)
{
	pst->a = (int*)malloc(sizeof(int) * 4);
	if (pst->a == NULL)
	{
		perror("malloc fail");
		return;
	}

	pst->top = 0;
	pst->capacity = 4;
}

int main()
{
	struct Stack st;
	StackInit(&st);
    // 插入100个数据

	return 0;
}

C语言的栈中:我明知要插入 100 个数据,还初始化容量为4,让他慢慢扩容。扩容是有消耗的

用 C语言 优化后:

void StackInit(struct Stack* pst, int defaultCapacity) // 默认初始化容量
{
	pst->a = (int*)malloc(sizeof(int) * 4);
	if (pst->a == NULL)
	{
		perror("malloc fail");
		return;
	}

	pst->top = 0;
	pst->capacity = defaultCapacity;
}

int main()
{
	struct Stack st;
	StackInit(&st, 100); // 插入100个数据

	return 0;
}

如果不知道要插入多少数据呢?        初始化过大浪费;过小不断扩,消耗也很大

C++ 缺省参数优化:更灵活

void StackInit(struct Stack* pst, int defaultCapacity = 4)// 开始不要给太大
{														  // 扩容就扩容,至少不要浪费
    pst->a = (int*)malloc(sizeof(int) * 4);
    if (pst->a == NULL)
    {
        perror("malloc fail");
        return;
    }

    pst->top = 0;
    pst->capacity = defaultCapacity;
}

int main()
{
    struct Stack st1;
    StackInit(&st1, 100); // 想控制也可以传

    struct Stack st2;
    StackInit(&st2); // 不知道插入多少个数据,你也不要传了

    return 0;
}

五. 函数重载

函数重载:C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。注:返回值没有要求

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

int main()
{
	cout << Add(1, 2) << endl; // 3
	cout << Add(1.1, 2.2) << endl; // 3.3
	return 0;
}

2. 参数类型顺序不同

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

int main()
{
	f(1, 'a');
	f('a', 1);
	return 0;
}

3. 参数个数不同

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

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

int main()
{
	f();
	f(10);
	return 0;
}

问题代码:是否构成重载?                构成               问题:无参调用存在歧义

无参和全缺省,语法上构成重载;现实中没人会把他俩写成重载

void f() // 无参
{
	cout << "f()" << endl;
}

void f(int a = 0) // 全缺省
{
	cout << "f(int a)" << endl;
}

int main()
{
	f(); // 报错:“f”: 对重载函数的调用不明确
	return 0;
}

C++支持函数重载的原理 - 名字修饰(name Mangling)

C语言中,符号表的名字直接用的函数名                Linux 下 gcc

C++中,函数符号表,函数修饰后变成:_Z+函数长度 +函数名+类型首字母                Linux 下 g++

不同的编译器,符号表中,对函数名修饰的结果是不一样的

六. 引用

指针:

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

                        eg:李逵,铁牛,黑旋风

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

int main()
{
	// 1.一个变量可以有多个引用
	int a = 0;
	int& b = a;
	int& c = b;

	// 2.引用在定义时必须初始化
	int& d; // 报错

	// 3.引用一旦引用一个实体,再不能引用其他实体
	int x = 10;
	c = x; // x的值赋给c,c依旧是a/b对象别名
           // 不是 c变成x的引用(别名)

	return 0;
}
int main()
{
	int a = 0;
	int& b = a;
	int& c = b;
	int& d = a;

	cout << &a << endl;
	cout << &b << endl;
	cout << &c << endl;
	cout << &d << endl;

	b++;
	d++;

	int x = 11;
	d = x;

	return 0;
}

1. 使用场景

1.1 做参数

        1、输出型参数:形参改变要影响实参
        2、提高效率:大对象,深拷贝类对象

void Swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
} // 形参是实参的别名,形参改变会影响实参

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

typedef struct ListNode
{
	int val;
	struct ListNode* next;
}LTNode, * PLTNode; // 下面有解析

//void ListPushBack(struct ListNode*& phead, int x)
//void ListPushBack(LTNode*& phead, int x)
void ListPushBack(PLTNode& phead, int x)
{
	// phead = newnode; 单链表中有一步要改变 phead ,因此 C语言 中要传2级指针
}

int main()
{
	int x = 0, y = 1;
	Swap(x, y);
	cout << x << " " << y << endl;

	int* px = &x, * py = &y;
	cout << px << " " << py << endl;
	Swap(px, py);
	cout << px << " " << py << endl;

	PLTNode plist = NULL;
	ListPushBack(plist, 1);

	return 0;
}

LTNode 是 struct ListNode 重命名后的
struct ListNode* 取别名为 PLTNode

1.2 做返回值

        1、提高效率:大对象,深拷贝类对象
        2、修改返回值+获取返回值

C语言中:

// 代码 1
int Count()
{
	int n = 0;
	n++;
	// ...
	return n;
}
int main()
{
	int ret = Count();
	return 0;
}

传值返回,n 不是直接传给 ret

        这里 n 在局部域,在栈帧里。出作用域,栈帧销毁,不能用 n 作为返回值
        编译器会自动生成临时变量(数据小,可能是寄存器,4/8 字节)n -> copy -> 临时变量 ->copy -> ret

// 代码 2
int Count()
{
	static int n = 0;
	n++;
	// ...
	return n;
}
int main()
{
	int ret = Count();
	return 0;
}

这里 n 在静态区,不受栈帧的影响,会不会生成临时变量?
        会,编译器不做特殊处理 n -> copy -> 临时变量 ->copy -> ret

传值返回生成临时变量,是因为传值返回。跟出了作用域 n 还在不在无关。

大对象、深拷贝类对象对效率影响会很大
能不能不再生成临时变量?—— 引用做返回值


静态对象引用返回:

int& Count()
{
	static int n = 0;
	n++;
	// ...
	return n;
}
int main()
{
	int ret = Count();
	return 0;
}

        Count 栈帧的销毁不影响 n

上面静态的,用引用返回没有问题。但不是都能用引用返回:返回局部对象的引用很危险

危险1:

int& Count()
{
	int n = 0;
	n++;
	// ...
	return n;
}

int main()
{
	int ret = Count(); // 1 / 随机值
	cout << ret << endl;

	return 0;
}

        返回的是 n 的别名。n 和 ret 是拷贝关系。返回值初始化给 ret
                                               这里栈帧销毁跟 ret 没关系,后面再调用函数,不会影响 ret

这里打印的 ret 是不确定的
如果 Count 函数结束,栈帧销毁,没有清理栈帧空间,那么 ret 结果侥幸是正确的
如果 Count 函数结束,栈帧销毁,清理栈帧,ret 结果是随机值

cout << ret << endl ; 访问的是一块已经销毁的空间上的变量

危险2:

int& Count(int x)
{
	int n = x;
	n++;
	// ...
	return n;
}

int main()
{
	int& ret = Count(10);
	cout << ret << endl; // 11 / 随机值

	Count(20); // 调同一个函数,栈帧是同样大的。n 还在这块空间,给20,就被覆盖成20
	cout << ret << endl; // 21 / 随机值

	return 0;
}

        这里返回的是 n 的别名。ret 是(n 的别名)的别名。《==》ret 是 n 的别名

        地址一样,是同一块空间

如果调用的是任意的其他函数:危险2:

int& Count(int x)
{
	int n = x;
	n++;
	// ...
	return n;
}

int main()
{
	int& ret = Count(10);
	cout << ret << endl;

	printf("八嘎\n");
	cout << ret << endl;

	return 0;
}

        空间被覆盖了

ret 能访问,但是没有保障。访问的是一块已经销毁的空间上的变量《==》野指针,只是没有报错

1.3 总结:

1. 基本任何场景都可以用引用传参
2. 谨慎用引用做返回值。出了函数作用域,对象不在了,就不能用引用返回,还在就可以用引用返回

1.4 应用场景

C语言

struct SeqList
{
	int a[100];
	size_t size;
};

int SLGet(SeqList* ps, int pos)
{
	assert(pos < 100 && pos >= 0);

	return ps->a[pos];
}

void SLModify(SeqList* ps, int pos, int x)
{
	assert(pos < 100 && pos >= 0);

	ps->a[pos] = x;
}

int main()
{
	SeqList s;
	SLModify(&s, 0, 1); // 给第0个位置赋值为1
	cout << SLGet(&s, 0) << endl; // 获取第0个位置的数据

	int ret1 = SLGet(&s, 0);
	SLModify(&s, 0, ret1+5); // 给第0个位置的数据+5

	return 0;
}

C++

int& SLAt(SeqList* ps, int pos)
{
	assert(pos < 100 && pos >= 0);

	return ps->a[pos];
}

int main()
{
	SeqList s;
	SLAt(&s, 0) = 1; // 给第0个位置赋值为1
	cout << SLAt(&s, 0) << endl; // 获取第0个位置的数据
	SLAt(&s, 0) += 5; // 给第0个位置的数据+5

	return 0;
}

出了作用域,返回对象 ps->a[pos] 还在,因为是结构体里面的数组,不是局部变量,是外面传过来的

更 C++

int& SLAt(SeqList& s, int pos)
{
	assert(pos < 100 && pos >= 0);

	return s.a[pos];
}

int main()
{
	SeqList s;
	SLAt(s, 0) = 1; // 给第0个位置赋值为1
	cout << SLAt(s, 0) << endl; // 获取第0个位置的数据
	SLAt(s, 0) += 5; // 给第0个位置的数据+5

	return 0;
}

2. 常引用问题

int main()
{
	const int a = 0;
	int& b = a; // 错。引用过程中,权限不能放大。b的改变会影响a

	const int c = 0;
	int d = c; // 对。c拷贝给d,没有放大权限。因为d的改变不影响c

	int x = 0;
	int& y = x; // 对。引用过程中,权限可以平移
	const int& z = x; // 对。引用过程中,权限可以缩小
	++z; // 错 下面解释
	++x; // 对 下面解释

    int& m = 10; // 错
    const int& m = 10; // 对。权限的平移。10是不能修改的,m变成10的别名

	return 0;
}

在梁山叫李逵(x)、铁牛(y),下山只叫黑旋风(z)        ==》        权限缩小了
李逵(x)、铁牛(y)可以吃肉、喝酒 黑旋风(z)只能吃肉,不能喝酒。
++z 相当于黑旋风(z)喝酒,错
++x 相当于李逵(x)、铁牛(y)喝酒 ==》则黑旋风(z)也喝了酒,黑旋风(z)的值也会发生改变

只缩小 z 作为别名的权限(只读),++z 错
用 z 不能改变,用 x y 能改变,++x 对。x y 的改变就是 z 的改变

int main()
{
	double dd = 11.11;
	int ii = dd; // 对。隐式类型转换
	int& rii = dd; // 错。
	const int& rii = dd; // 对。

	return 0;
}

发生类型转换(强制、隐式、类型提升、截断)时,中间会产生临时变量《==》传值返回

操作符两边的类型不同,会发生整型提升,且不能改变变量的数据
所以会生成临时变量(被提升的)。用临时变量与没有进行提升的运算

临时变量具有常性(相当于让 const 修饰了)
所以 int& rii = dd ; 错的原因是权限的放大,不是因为类型不同

int func1()
{
	static int x = 0;
	return x;
}

int main()
{
	int ret1 = func1(); // 对  拷贝
	int& ret2 = func1(); // 错  权限的放大
	const int& ret3 = func1(); // 对  权限的平移

	return 0;
}

int& func2()
{
	static int x = 0;
	return x; // 没产生临时变量,返回的是 x的别名
}

int main()
{
	int& ret2 = func2(); // 对  ret2给别名取别名,还是 x的别名		权限的平移
	const int& ret3 = func2(); // 对  权限缩小

	return 0;
}

3. 引用和指针的区别

底层汇编指令实现的角度看,引用是类似指针的方式实现的,要开空间。
但学习过程中,通常不认为引用开空间

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

七. 内联函数

C语言中,如果一个函数要频繁调用,每次调用都会建立栈帧,消耗大

int Add(int x, int y)
{
	return (x + y) * 10;
}

int main()
{
	for (int i = 0; i < 10000; i++)
	{
		cout << Add(i, i + 1) << endl;
	}

	return 0;
}

简化:宏函数替换

#define ADD(x, y) (((x) + (y)) * 10)

//#define Add(int x, int y) return (x+y);
//#define Add(x, y) x+y
//#define Add(x, y) (x+y)
//#define Add(x, y) ((x)+(y))

//	if (Add(10, 20))
//	{
//
//	}
//
//	Add(10, 20) * 20;    // x+y
//
//	int a = 1, b = 2;
//	Add(a | b, a & b); // (a | b + a & b)    // (x+y)

优点-- 不需要建立栈帧,提高调用效率;复用性
缺点-- 复杂,容易出错、可读性差;不能调试(预处理阶段替换了);没有类型安全的检查

C++ 内联函数:

inline int Add(int x, int y)
{
	return (x + y) * 10;
}

int main()
{
	for (int i = 0; i < 10000; i++)
	{
		cout << Add(i, i + 1) << endl;
	}

	return 0;
}

关键字 inline 修饰。编译时C++编译器会在调用内联函数的地方展开(用函数体替换函数的调用)。不建立栈帧,提升效率

inline 很好,但不能乱用。只适用短小的频繁调用的函数。太长的函数会导致代码膨胀

默认debug模式下,inline不会起作用,否则不方便调试了

inline对于编译器仅仅只是一个建议,最终是否成为inline,编译器自己决定
像类似函数就加了inline也会被否决掉:1. 比较长的函数 2. 递归函数

注意:

内联函数不能声明、定义分离。他认为用的地方都会展开,内联函数是不需要被 call 的,所以没有地址,不会进符号表
分离会导致链接问题        直接定义在 .h

// Func.h
#include <iostream>
using namespace std;

inline void f(int i);

// Func.cpp
#include "Func.h"

void f(int i)
{
	cout << i << endl;
}

// Test.cpp
#include "Func.h"

int main()
{
	f(10);
	return 0;
}

链接错误:Test.obj : error LNK2019: 无法解析的外部符号 "void __cdecl f(int)" (?f@@YAXH@Z),函数 _main 中引用了该符号

正确写法:

// Func.h
#include <iostream>
using namespace std;

inline void f(int i)
{
	cout << i << endl;
}

// Test.cpp
#include "Func.h"

int main()
{
	f(10);
	return 0;
}

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

auto:长类型替换

int main()
{
	int a = 0;
	int b = a;
	auto c = a; // 根据右边的表达式自动推导c的类型
	auto d = 1 + 1.11; // 根据右边的表达式自动推导d的类型
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;

	return 0;
}

#include <iostream>
#include <map>
#include <string>
#include <vector>
using namespace std;

int main()
{
	vector<int> v;

	// 类型很长
	//vector<int>::iterator it = v.begin();
	// 等价于
	auto it = v.begin();

	std::map<std::string, std::string> dict;
	//std::map<std::string, std::string>::iterator dit = dict.begin();
	// 等价于
	auto dit = dict.begin();

	return 0;
}

1. auto 使用细则

auto 定义变量必须初始化编译阶段,编译器根据初始化表达式来推导 auto 的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型

(1)auto、指针、引用合起来用

int main()
{
	int x = 10;
	auto a = &x; // 根据右边推出来
	auto* b = &x; // 指定必须是指针
	//auto* f =10; // 报错

	auto& c = x; // 指定必须是引用

	return 0;
}

(2)同一行定义多个变量

void TestAuto()
{
    auto a = 1, b = 2; 
    auto c = 3, d = 4.0;  // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}

在同一行声明多个变量时,这些变量必须是相同的类型
因为编译 器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量

2. auto 不能推导的场景

(1)auto 不能作为函数的参数

// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a) // 栈帧都不知道开多大
{	}

(2)auto不能直接用来声明数组

void TestAuto()
{
    int a[] = {1,2,3};
    auto b[] = {4,5,6}; // 报错
}

(3)为了避免与 C++98中的 auto 发生混淆,C++11只保留了 auto 作为类型指示符的用法

(4)auto 优势用法是跟 C++11 提供的新式 for 循环,还有 lambda 表达式等 配合使用

九. 范围 for(C++ 11)

适用于数组、容器

1. 语法

for ( 范围内用于迭代的变量 :被迭代的范围 )

与普通循环类似,可以用continue来结束循环,也可以用break来跳出整个循环

C语言:

int main()
{
	int arr[] = { 1, 2, 3, 4, 5 };

	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
		arr[i] *= 2; // 写

	for (int* p = arr; p < arr + sizeof(arr) / sizeof(arr[0]); ++p)
		cout << *p << " "; // 读
	cout << endl;

	return 0;
}

C++:

int main()
{
	int arr[] = { 1, 2, 3, 4, 5 };

	// 依次取数组中数据赋值(拷贝)给e  自动迭代,自动判断结束
	// for (int e : arr) double的数组就得改
	for (auto e : arr)
	{
		cout << e << " ";
	}
	cout << endl;

    
	// 修改数据
	for (auto& e : arr)
	{
		e *= 2;
	}

	for (auto e : arr)
	{
		cout << e << " ";
	}
	cout << endl;

	return 0;
}

修改数据,不能写成:for (auto e : arr)

        自动推导 e 的类型,依次取数组中数据赋值(拷贝)给 e。e 的改变不会影响数组的内容

且不能配指针,依次取数据,数据不能给指针

2. 使用条件

1. for循环迭代的范围必须是确定的

        对于数组而言,就是数组中第一个元素和最后一个元素的范围
        对于类而言,应该提供 begin 和 end 的方法,begin 和 end 就是for循环迭代的范围

void TestFor(int arr[]) // 这个 arr是指针,不是数组
{
    for(auto& e : arr)
        cout<< e <<endl;
}

2. 迭代的对象要实现++和==的操作

十. 空指针 nullptr(C++ 11)

(1) 使用 nullptr 表示空指针时,不需要包含头文件,因为 nullptr 是 C++11 作为新关键字引入的
(2) 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同 nullptr <==> (void*)0
(3) 以后空指针用 nullptr

C++98中,NULL 是宏。在传统的C头文件(stddef.h)中,可以看到如下代码:

void f(int) // C/C++编译器没有规定必须形参接收
{
	cout << "f(int)" << endl;
}

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

int main()
{
	f(0);
	f(NULL); // 本意想调 int*   但失误了
	f((int*)NULL);
	f(nullptr);

	return 0;
}

本篇的分享就到这里了,感谢观看,如果对你有帮助,别忘了点赞+收藏+关注
小编会以自己学习过程中遇到的问题为素材,持续为您推送文章

评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值