C++入门

C++是兼容C语言的,因此C语言的所有语法在C++中都是可以使用的

C语言是存在不少缺陷的,因此C++就对很多缺陷做了改进,产生了一些新的语法和机制

目录

一、命名空间

C语言缺陷:命名冲突

命名空间

展开命名空间

全部展开

部分展开

命名空间的嵌套

命名空间的合并

C++第一个程序,向世界说“你好”

二、缺省参数

全缺省

半缺省

缺省参数应用

三、函数重载

四、引用

引用特点

引用实际应用

常引用

五、内联函数

六、auto关键字

七、基于范围的for循环

八、指针空值nullptr(C++11)


一、命名空间

C语言缺陷:命名冲突

C++入门_第1张图片

rand本是库函数,但是我们不知道,定义了rand变量,导致了命名冲突

命名空间

命名空间本质就是划分界限,命名空间内部的成员外部是不能随意访问的

命名空间 使用格式:

关键字namespace + 命名空间名字(随便起)

{

       //具体成员

}

#include
#include
namespace dck //定义一个叫dck的命名空间
{
	int rand = 0;
}

int main()
{
	printf("%p\n",rand);
}

那如果想访问命名空间内部的成员呢? ---  使用域作用限定符 ::    

使用方法:命名空间名字::成员

#include
#include
namespace dck //定义一个叫dck的命名空间
{
	int rand = 0;
}

int main()
{
	printf("%d\n", dck::rand); 
}

命名空间中可以定义变量,函数,类(理解为结构体)

#include
namespace dck
{
	//定义变量
	int rand = 10;
	//定义函数
	int Add(int left, int right)
	{
		return left + right;
	}
	//定义结构体
	struct Node
	{
		struct node* next;
		int val;
	};
}
int main()
{
	printf("%d\n", dck::rand);
	printf("%d\n", dck::Add(1, 2));
	//使用命名空间中的结构体注意::位置
	struct dck::Node node;
}

展开命名空间

全部展开

每次使用命名空间中的成员都要加域作用限定符,未免有些麻烦,我们可以直接展开命名空间(相当于授权访问),这样的话外部就可以直接访问命名空间内部成员了,相当于命名空间失效了

全部展开命名空间方法:  using  namespace  命名空间名字

#include
namespace dck
{
	int rand = 10;

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

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

using namespace dck; //展开命名空间---相当于命名空间失效了

int main()
{
	printf("%d\n", rand);
	printf("%d\n", Add(1, 2));
	struct Node node;
}

部分展开

部分展开指定是授权命名空间中的特定成员,其他成员的依然不能直接访问

部分展开命名空间方法:  using  命名空间名字 :: 需要授权的成员

#include
namespace dck
{
	int rand = 10;

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

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

using dck::Add;  //部分展开

int main()
{
	printf("%d\n", Add(1, 2)); //只能直接访问Add
	//其他成员仍需要域作用限定符::
	printf("%d\n", dck::rand);
	struct dck::Node node;
}

命名空间的嵌套

#include
namespace dck
{
	int rand = 10;
	namespace xxx
	{
		int rand = 20;
	}
}
int main()
{
	printf("%d\n", dck::rand);
	printf("%d\n", dck::xxx::rand); //命名空间的嵌套访问
}

命名空间的合并

Stack.h

#include
using namespace std;
namespace dck
{
	void StackInit()
	{
		cout << "void StackInit()" << endl;
	}
}

Queue.h

#include
using namespace std;
namespace dck
{
	void QueueInit()
	{
		cout << "void QueueInit()" << endl;
	}
}

Test.cpp

#include"Queue.h"
#include"Stack.h"
int main()
{
	dck::QueueInit();
	dck::StackInit();
}

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

Test.cpp中包含了两个头文件之后 两个头文件中的命名空间dck 会进行合并,成员就有两个函数

C++第一个程序,向世界说“你好”

写法一: 全部展开

#include 
using namespace std;
int main()
{
	 cout << "hello world" << endl; 
}

1.iostream表示出入输出流,是C++标准头文件

2.cout 是标准输出的意思(console out--- 控制台输出) ,相当于 printf;

   endl = end of line,相当于"\n",表示换行

3.cout 和 endl 等库函数都是封装在头文件中的std标准命名空间中的,因此使用时不但需要包含#include头文件, 还需要展开std命名空间

4. << 是流插入运算符,形象理解, "hello world" 流向了 cout ,也就是输出到了屏幕上

写法二:部分展开

#include 
using std::cout;
using std::endl;
int main()
{
	 cout << "hello world" << endl; 
}

写法三:域作用限定符

#include 
int main()
{
	 std::cout << "hello world" << std::endl; 
}

拓展:

1.cout 对应于 printf, 同时cin 对应于 scanf

2. >> 流提取操作符

#include 
using std::cin;
using std::cout;
using std::endl;
int main()
{
	int i = 0;
	cin >> i; //键盘输入值存到变量i中
	cout << i*2 << endl;
}

3. cin 和 cout 可以自动识别数据类型(相比C语言scanf 和 printf 需要指定格式"%d"等等)

#include 
using namespace std;
int main()
{
	int i;
	double j;
	cin >> i >> j; //连续输入
	cout << i << " " << j << endl; //连续输出
}

4.C++控制浮点数精度比较麻烦,我们直接采用使用C语言方式即可

#include 
using namespace std;
int main()
{
	double i = 3.34;
	printf("%.1f", i); //小数点后保留一位小数
}

二、缺省参数

#include
using namespace std;
void Fun(int a = 1)
{
	cout << a << endl;
}
int main()
{
	Fun(2); //2
	Fun(); //1
}

函数定义时参数给了默认值就成为了缺省参数, 此时调用函数如果不传值就使用默认值,传了实参就使用实参值~

全缺省

函数的所有形参都是缺省参数

#include
using namespace std;
void Fun(int a = 10, int b = 20, int c = 30)
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl << endl;
}
int main()
{
	Fun(1, 2, 3);
	Fun();
	Fun(1);
	Fun(1, 2);
	Fun(1, 2, 3);
}

半缺省

函数的部分形参是缺省参数,要求从右往左给缺省值,而且调用函数传参时不能跳跃传参

#include
using namespace std;
void Fun(int a, int b = 20, int c = 30)
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl << endl;
}
int main()
{
	Fun(1);
	Fun(1, 2);
	Fun(1, 2, 3);
    Fun(1, ,3); //跳跃传参会报语法错误
}

缺省参数应用

栈的实现---未用缺省参数

#include
#include
#include
typedef struct Stack
{
	int* a;
	int top;
	int capacity;
}ST;

void StackInit(ST* pst)
{
	pst->a = (int*)malloc(sizeof(int) * 4);
	if (pst->a == NULL)
	{
		perror("malloc fail\n");
		exit(-1);
	}
	pst->top = 0;
	pst->capacity = 4;
}

void StackPush(ST* pst, int x)
{
	if (pst->top == pst->capacity)
	{
		int newCapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
		int* tmp = (int*)realloc(pst->a, sizeof(int) * newCapacity);
		if (tmp == NULL)
		{
			perror("realloc fail\n");
			exit(-1);
		}
		pst->a = tmp;
		pst->capacity = newCapacity;
	}
	pst->a[pst->top++] = x;
}

int STTop(ST* pst)
{
	return pst->a[pst->top - 1];
}

void STPop(ST* pst)
{
	assert(pst->top > 0);
	pst->top--;
}

int main()
{
	Stack st;
	StackInit(&st);
	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 3);
	StackPush(&st, 4);
	StackPush(&st, 5);
	StackPush(&st, 6);
	StackPush(&st, 7);
	printf("%d ", STTop(&st));
}

栈的实现---使用缺省参数

为了避免频繁扩容,我们可以在实现初始化函数时使用默认参数,初始化时就把空间开好

#include
#include
#include
typedef struct Stack
{
	int* a;
	int top;
	int capacity;
}ST;

void StackInit(ST* pst, int N = 4)
{
	pst->a = (int*)malloc(sizeof(int)*N);
	if (pst->a == NULL)
	{
		perror("malloc fail\n");
		exit(-1);
	}
	pst->top = 0;
	pst->capacity = N;
}

void StackPush(ST* pst, int x)
{
	pst->a[pst->top++] = x;
}

int STTop(ST* pst)
{
	return pst->a[pst->top - 1];
}

void STPop(ST* pst)
{
	assert(pst->top > 0);
	pst->top--;
}

int main()
{
	//初始化开辟1000个int大小空间
	Stack st1;
	StackInit(&st1, 1000);
	for (int i = 0;i < 1000;i++)
	{
		StackPush(&st1, i);
	}
	for (int i = 0;i < 1000;i++)
	{
		printf("%d ", STTop(&st1));
		STPop(&st1);
	}

	//初始化开辟100个int大小空间
	Stack st2;
	StackInit(&st2, 100);
	for (int i = 0;i < 100;i++)
	{
		StackPush(&st2, i);
	}
	for (int i = 0;i < 100;i++)
	{
		printf("%d ", STTop(&st2));
		STPop(&st2);
	}

	//不知道开辟多大空间
	Stack st3;
	StackInit(&st3);
	StackPush(&st3, 1);
	StackPush(&st3, 2);
	//···
}

 ps:如果含缺省参数的函数在不同文件中分别有定义和声明,规定声明给缺省值,定义不给缺省值

三、函数重载

概念 : 函数名相同,参数不同,则称构成了函数重载

情形一:参数类型不同

#include
using namespace std;
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;
	cout << Add(1.1, 2.2) << endl;
}

情形二:参数个数不同

#include
using namespace std;
void f()
{
	cout << "f()" << endl;
}
void f(int a)
{
	cout << "f(int a)" << endl;
}
int main()
{
	f();
	f(1);
}

情形三:参数类型顺序不同

#include
using namespace std;
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, 'w');
	f('w', 1);
}

ps:函数仅返回值不同不构成函数重载, 编译器会报错

#include
using namespace std;
//以下两个函数不构成函数重载
int func(int left, int right)
{
	return left + right;
}
float func(int left, int right)
{
	return (float)(left + right);
}
int main()
{
	func(1, 2); 
}

案例:判断一下函数是否构成函数重载:

1.不构成函数重载,函数重载要求函数在同一个域中

namespace dck1
{
	void fun(int x)
	{}
}
namespace dck2
{
	void fun(double x)
	{}
}

2.构成函数重载,命名空间名字相同会自动合并

namespace dck1
{
	void fun(int x)
	{}
}
namespace dck1
{
	void fun(double x)
	{}
}

3.构成函数重载(缺省参数也是参数呀),但是可能会存在调用歧义

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

void fun(int a, int b = 1)
{
	cout << "void fun(int a, int b)" << endl;
}
int main()
{
	fun(1, 2); //正确
	fun(1); //调用歧义,编译器不知道要调用第一个函数还是调用第二个函数
}

四、引用

引用就是给已存在的变量起别名,因此表示的是同一个变量

int main()
{
	int a = 1;
	//给a起了一个别名,叫做b,专业术语,叫b引用了a
	int& b = a; //固定写法:int&是b的类型,称为引用类型
	cout << a << endl;
	cout << b << endl;

	//类似地
	int* p = &a;  //p类型是int*
	int*& pa = p; //pa引用了p,类型是int*&

	//a, b共用一块空间
	cout << &a << endl;
	cout << &b << endl;

	//a,b同时改变
	a++;
	cout << a << endl;
	cout << b << endl;
}

引用特点

1.定义时必须初始化

起别名当然得有对象了,不然是给谁起的别名呢???

int main()
{
	int& b; //未初始化(×)
}

2.一个变量可以有多个别名(合情合理)

int main()
{
	int a = 1;
	//a的别名有b,c,d
	int& b = a;
	int& c = a;
	int& d = a;
}

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

int main()
{
	int a = 1;
	int& b = a; // b引用了a
	cout << &b << endl; //0000008CA436F7B4

	int c = 2;
	cout << &c << endl; //0000008CA436F7F4

	b = c; //此时b并没有引用a, 只是c赋值给了b
	cout << &b << endl; //0000008CA436F7B4

	// b地址没变,证明了一旦引用一个实体,不能再引用其他实体
}

引用实际应用

引用所有的应用都是围绕指针展开的,也就是说引用其实是对指针用途的一种替换,而且可能会比指针操作更加简单易懂

1.引用做参数

eg:交换两变量

void Swap(int& p1, int& p2)
{
	int tmp = p1;
	p1 = p2;
	p2 = tmp;
}
int main()
{
	int a = 10, b = 20;
	cout << "交换前: a =" << a << " " << "b = " << b << endl;
	Swap(a, b);
	cout << "交换后: a =" << a << " " << "b = " << b << endl;
}

2.引用做返回值

eg:顺序表修改第i个位置的值

C语言指针实现

#include
using namespace std;
typedef struct SeqList
{
	int* a;
	int size;
	int capacity;
}SL;

void SLInit(SL* ps)
{
	ps->a = (int*)malloc(sizeof(int) * 4);
	ps->size = 0;
	ps->capacity = 4;
}
//读取第i个位置的值
int SLAT(SL* ps, int i)
{
	return ps->a[i];
}
//修改第i个位置的值
void SLModify(SL* ps, int i, int x)
{
	ps->a[i] = x;
}

int main()
{
	SL s;
	SLInit(&s);
	SLAT(&s, 2);
	SLModify(&s, 2, 10);
	cout << SLAT(&s, 2) << endl;
}

C++引用实现

#include
using namespace std;
typedef struct SeqList
{
	int* a;
	int size;
	int capacity;
}SL;

void SLInit(SL& ps)
{
	ps.a = (int*)malloc(sizeof(int) * 4);
	ps.size = 0;
	ps.capacity = 4;
}

int& SLAT(struct SeqList& ps, int i)
{
	return ps.a[i];
}

int main()
{
	SL s;
	SLInit(s);
	SLAT(s, 2) = 10;
	cout << SLAT(s, 2) << endl;
}
ps:引用做返回值不能随便用,是有一定条件的

    函数调用完之后,函数栈帧就销毁了,这时如果返回的是引用,就可能导致保存了已经销毁的变量的值,出现随机值或者未定义等行为

int& Count()
{
	int n = 0;
	n++;
	return n;
}
int main()
{
	int ret = Count(); //返回的是一个已经销毁的变量的引用
	//打印结果可能是1,也可能是随机值
	cout << ret << endl;  
	return 0;
}

总结:传引用传参&&传引用返回意义
传引用传参意义(任何时候都可以)
1.提高效率
2.输出型参数(leetcode常见题型,该参数存在的意义不是为了函数内部使用,而是给外界反馈,达到修改外面变量的目的)
传引用返回意义(是有条件的,出函数作用域对象还在适合用传引用返回)
1.提高效率
2.修改返回对象

常引用

规定,在引用过程中(赋值不受权限限制),权限可以缩小,可以平移,但是不能放大

1.权限放大:(×)

int main()
{
	//权限的放大(×)
	const int a = 0; //a被const修饰了,表明a不能被修改
	int& b = a; //(×) b没有被const修饰,表明b可以被修改,此时就叫做权限放大了,会报错
}

2.权限平移:(√)

int main()
{
	const int a = 0; //a不能修改
	const int& b = a; //b不能修改
}

3.权限缩小:(√)

int main()
{
	int a = 0;  //a可以修改
	const int& b = a;  //b不能修改
}

ps:临时变量与权限放大问题

1.数据拷贝是借助临时变量实现的

int main()
{
	//赋值不受权限限制
	int x = 0;
	double y = x;//(√)

	//数据的拷贝是借助临时变量实现的,而临时变量具有常属性
	int x = 0;  //x并没有被cons修饰
	double& y = x; //(×)
	//x拷贝给y其实是先把x值保存到了一个临时变量里面
	//而临时变量具有常属性(被const修饰),因此权限放大了
	const double& y = x;  //权限平移(√)
}

2. 函数传值返回借助临时变量实现

int func()
{
	int a = 0;
	return a;
}
int main()
{
	int& ret = func(); //(×)
	const int& ret = func(); //(√)
}

引用和指针的区别:

1.引用在语法概念是一个别名,没有独立空间,和其引用实体共用一块空间;但在底层实现上,引用是占据空间的, 因为引用是按照指针的方式来实现的

2.引用必须初始化,指针没有要求

3.引用一旦引用了一个实体,就不能引用其他实体,而指针随时可以改变指向

4.有多级指针,却没有多级引用

5.引用比指针使用起来更加安全

6.有空指针,但没有空引用

7.sizeof(引用)计算的是引用实体的大小,sizeof(指针)计算的是地址空间的大小(恒为4/8个字节)

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

五、内联函数

C语言介绍过宏,而宏是由缺陷的,C++中发明了内联函数可以替代宏,被关键字inline修饰的函数就称为内联函数,内联函数的特点是在调用内联函数的地方函数会直接展开,直接执行函数体的内容,没有函数调用时函数栈帧的开销,提高了程序的运行效率

1.内联函数函数体比较小,且不能包含递归调用,否则直接展开会代码膨胀,导致目标文件太大

2.inline只是向编译器发出请求,编译器有可能会忽略这个请求(当内联函数体过长等等)

3.inline不能声明和定义分离在两个文件中,会导致链接错误

#include
using namespace std;
inline int Add(int left, int right)
{
	return left + right;
}
int main()
{
	int ret = 0;
	ret = Add(1, 2); //Add函数体内容会直接在该处展开,不会去调用Add函数
	printf("%d\n", ret);
}

六、auto关键字

auto可以根据变量初始化表达式自动推导变量类型,无需显示指定变量类型

#include
using namespace std;
typedef char* pstring;
int main()
{
	int a = 1;
	auto b = a;
	auto* c = &a; //与 auto c = &a 等价
	auto& d = a; //auto声明引用类型必须加&
	cout << "a:" << typeid(a).name() << endl; //typeid用于查看变量类型
	cout << "a:" << typeid(b).name() << endl;
	cout << "a:" << typeid(c).name() << endl; 
	cout << "a:" << typeid(d).name() << endl; 
}

注意:

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

#include
using namespace std;
typedef char* pstring;
int main()
{
	//在同一行定义多个变量
	auto a = 1, b = 2;//(√)
	auto c = 3, d = 4.5 //(×)
}

2.auto不能作为函数参数,也不能声明数组

#include
using namespace std;
typedef char* pstring;
void func(auto x) //(×)
{
	//···
}
int main()
{
	func(1);
	auto arr[] = { 1, 2, 3 };//(×)
}

七、基于范围的for循环

基本用法:

#include
using namespace std;
typedef char* pstring;
int main()
{
	int arr[5] = { 1, 2, 3, 4, 5 };
	for (auto e : arr) //依次取数组中的值赋值给e,自动判断结束,自动迭代
	{
		cout << e << " ";
	}
}

注意:基于范围的for循环只是把数组元素依次拿出来了

#include
using namespace std;
typedef char* pstring;
int main()
{
	int arr[5] = { 1, 2, 3, 4, 5 };
	//arr中的值没有改变
	for (auto e : arr) 
	{
		e *= 2;
	}
	for (auto e : arr)
	{
		cout << e << " ";
	}
}
#include
using namespace std;
typedef char* pstring;
int main()
{
	int arr[5] = { 1, 2, 3, 4, 5 };
	//arr中的值改变了
	for (auto& e : arr) 
	{
		e *= 2;
	}
	for (auto e : arr)
	{
		cout << e << " ";
	}
}

八、指针空值nullptr(C++11)

我们知道C语言中是用NULL表示空指针,那么为什么C++要引入nullptr表示空指针呢?还是由于当初定义NULL时的缺陷

#include
using namespace std;
typedef char* pstring;
void func(int i)
{
	cout << "func(int)" << endl;
}
void func(int* p)
{
	cout << "func(int)" << endl;
}
int main()
{
	func(0); //打印"func(int)"
	func(NULL); //打印"func(int)"
}

我们本意是想构成函数重载,但是由于NULL被定义成了字面常量0或(void*)0,导致了上述问题

因此引入了关键字nullptr表示空指针,无需包含任何头文件

·································································································································

白日不到处,青春恰自来。苔花如米小,也学牡丹开,以此共勉~

C++入门_第2张图片

 

你可能感兴趣的:(c++)