C++入门


1. 命名空间

有的时候,当我们遇到变量重名时,无法解决使用哪个变量,这里我们引申命名空间来解决这个问题

a. 命名空间的定义

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

代码举例1

#include<iostream>
int a = 0;
int main()
{
	int a = 10;
	printf("%d\n",a);
	printf("%d\n", ::a);// 在全局里面找 a 变量
}

一般来说,找寻变量,会先在局部里面找,如果局部里面没有,才会到全局里面找

代码举例2

namespace A
{
	typedef struct book
	{
		int a;
	}TB;
	int d;
}

namespace B
{
	typedef struct book
	{
		int b;
	}TB;
}

namespace C
{
	struct book
	{
		int c;
	};
}

#include "test1.h"
#include "test2.h"
#include "test3.h"
#include<iostream>
using namespace std; //全局展开
using C::book;
using A::TB;
using A::d;
int main()
{
    TB stu1;
    B ::TB stu2;
    struct C::book stu3;

    cin >> stu1.a;
    cin >> stu2.b;
    cin >> stu3.c;
    cin >> d;

    cout << "hello bit" << endl;
    cout << "hello bit" << '\n';
    cout << "hello bit" << ' ';
    cout << endl;
    cout << stu1.a << endl;
    cout << stu2.b << endl;
    cout << stu3.c << endl;
    cout << d << endl;
    return 0;
}

b. 命名空间的使用的方式

  • 加命名空间名称及作用域限定符

int main()

{

printf("%d\n", N::a);

return 0;

}

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

using N::b;

int main()

{

printf("%d\n", b);

return 0;

}

  • 使用using namespace 命名空间名称 引入( 相当于将命名空间展开了)

using namespce N;

int main()

{

printf("%d\n", b);

return 0;

}

2. 输入与输出

代码举例1

#include<iostream>
// std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中
using namespace std;
int main()
{
cout<<"Hello world!!!"<<endl;
return 0;
}

代码举例2

#include<iostream>
using namespace std;
int main()
{
	int arr[5];
	for (int i = 0; i < 5; i++)
	{
		cin >> arr[i];
	}
	for (int i = 0; i < 5; i++)
	{
		cout << arr[i] << ' ';
	}
	return 0;
}

说明:

  1. 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件
    以及按命名空间使用方法使用std。
  2. cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含< iostream >头文件中。
  3. <<是流插入运算符,>>是流提取运算符。
  4. 使用C输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。
    C的输入输出可以自动识别变量类型。
  5. 实际上cout和cin分别是ostream和istream类型的对象,>>和<<也涉及运算符重载等知识,

3. 缺省参数

a. 缺省参数的概念:

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

b. 缺省参数的分类:

  • 全缺省参数
  • 半缺省参数

全缺省参数:

代码举例1

#include<iostream>
using namespace std;
void ADD(int x = 0)
{
    cout << x << endl;
}
int main()
{
    ADD(10); // n = 10
    ADD();
    return 0; // n = 0
}

代码举例2

#include<iostream>
using namespace std;
void Mul(int x = 10 ,int y = 20) // 全缺省参数
{
    cout << x << endl;
    cout << y << endl;
}

半缺省参数:

代码举例3

void Mul(int x ,int y = 20) //半缺省参数
{
    cout << x << endl;
    cout << y << endl;
}

注意事项:

  1. 半缺省参数必须从右往左(连续的)依次来给出

  2. 缺省参数不能在函数声明和定义中同时出现**(在声明的时候给缺省参数)**

  3. 缺省值必须是常量或者全局变量

  4. C语言不支持(编译器不支持)

代码举例4

#include<iostream>
using namespace std;
void ADD(int x = 0)
{
    cout << x << endl;
}
void Sub(int x = 0, int y = 1)
{
    cout << x << endl;
    cout << y << endl;
}
void Mul(int x ,int y = 20)
{
    cout << x << endl;
    cout << y << endl;
}
int main()
{
    ADD(10);
    ADD();
    Sub(10, 20);
    Sub(10);
    Mul(20);
    return 0;
}

4. 函数重载

当遇到函数名相同的时候,c语言无法解决这个问题,而C++允许在同一作用域中 声明几个功能类似的同名函数 ,这些同名函数的形参列表 (参数个数 或 类型 或 类型顺序) 不同,常用来处理实现功能类似数据类型不同的问题。

举例

#include<iostream>
using namespace std;
void Add(int x, int y)
{
	cout << x + y << endl;
}
void Add(double x, double y)
{
	cout << x + y << endl;
}
void Add(int x)
{
	cout << x << endl;
}
int main()
{
	Add(1, 2);
	Add(1.2, 2.0);
	return 0;
}

原理:

本来是通过函数名来找到对应的函数地址,但在编译处理时,c++编译器会对函数名进行重新修饰,不同的编译器处理是不一样的

如:

gcc编译器会对 Add(int x,int y) 编译成:

_3Addii

注意事项:

如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办法区分。

5. 引用

a. 引用的概念

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

代码举例

#include<iostream>
using namespace std;
int main()
{
	int a = 10;
	int& b = a; // b 和 a 是同一块空间的
    int& c = b;
	b++;
	cout << a << endl;
	cout << b << endl;
	return 0;
}

运行结果:

b. 引用的实例

  • 做参数

代码举例

#include<iostream>
using namespace std;
void Add(int &x, int &y)
{
	int tmp = x;
	x = y;
	y = tmp;
}
int main()
{
	int a = 10;
	int b = 20;
	Add(a, b);
	cout << a << endl;
	cout << b << endl;
	return 0;
}
  • 做返回值

代码举例1

#include<iostream>
using namespace std;
int& add()
{
	static int c = 3;
	c++;
	return c;
}
int main()
{
	int& d = add();
	cout << d << endl;
	add();
	cout << d << endl;

	return 0;
}

运行结果:

分析:

被 static 修饰的 c 生命周期发生了改变,由栈区存储到静态区。出了函数,并不会被销毁

返回的是 c 的别名(这样的好处是,避免了开辟空间。因为局部变量出了作用域会被销毁,所以返回值的时候,一定会先把值存到寄存器中,然后销毁函数栈帧,而直接返回变量的别名,就不需要存给寄存器了

d 也是 c 的别名。c 改变,d也改变

代码举例2

#include<iostream>
using namespace std;
int& add(int& x)
{
	return x;
}
int main()
{
	int arr[5];
	for (int i = 0; i < 5; i++)
	{
		add(arr[i]) = i * 10;
	}
	for (int i = 0; i < 5; i++)
	{
		cout << arr[i] << endl;
	}
	return 0;
}

运行结果:

分析:

x 是 arr[i] 的别名,返回的是 x 的别名,即arr[i]的别名

代码举例3

#include<iostream>
using namespace std;
struct a
{
	int size;
	int& add(int& x)
	{
		return x;
	}
	int capacity;
};

int main()
{
	struct a c;
	int arr[5];
	for (int i = 0; i < 5; i++)
	{
		c.add(arr[i]) = i * 10;
	}
	for (int i = 0; i < 5; i++)
	{
		cout << arr[i] << endl;
	}
	return 0;
}

运行结果:

注意事项:

c++允许结构体的成员可以是一个函数

主要事项:

  1. 如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。
  2. 引用多一个运用场景:结构体的成员可以是一个函数

(代码举例3)

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

c. 常引用

面临三种可能:

  • 权限的放大**(只有这个引用做不到)**(代码举例1,5)
  • 权限的平移(代码举例2)
  • 权限的缩小(代码举例3,4)

代码举例1

#include<iostream>
using namespace std;
int main()
{
	const int a = 10; 
	int& b = a; //权限的放大
	return 0;
}

分析:

代码举例2

#include<iostream>
using namespace std;
int main()
{
	int a = 10;
	int& b = a; // 权限的平移
	return 0;
}

代码举例3

#include<iostream>
using namespace std;
int main()
{
	int a = 10;
	const int& b = a; //权限的缩小
	return 0;
}

代码举例4

#include<iostream>
using namespace std;
int main()
{
	int a = 10;
	const int& b = a;
	a++;
	cout << a << endl;
	cout << b << endl;
	return 0;
}

运行结果:

#include<iostream>
using namespace std;
int main()
{
	int a = 10;
	const int& b = a;
	b++;
	cout << a << endl;
	cout << b << endl;
	return 0;
}

代码举例5

#include<iostream>
using namespace std;
int main()
{
	int a = 10;
	double& i = a;
	return 0;
}
#include<iostream>
using namespace std;
int main()
{
	int a = 10;
	const double& i = a;
	return 0;
}

分析:

这是一种强制类型转换 ,实际上,在转换的时候,会创建一个临时变量 (临时变量是已经转换过类型了,而原变量的类型不会因此而改变),临时变量具有常性,不能被修改 ,而引用拿到的别名是创建的临时变量 ,第一种写法实质上是扩大了权限

d. 值和引用的作为返回值类型的性能比较

我们提过一次,在可以用引用做返回值的前提下(注意事项第一条),引用的性能比值返回会更好( b. 代码举例1)

e. 引用和指针的区别

引用和指针的不同点:

  1. 引用概念上定义一个变量的别名,指针存储一个变量地址。

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

  3. 引用 在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体 (所以链表的构建中:next 只能是下一个节点的地址,而不能是下一个节点的别名 [链表的删除会出大问题] )

  4. 没有 NULL 引用,但有NULL指针

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

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

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

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

  9. 引用比指针使用起来相对更安全

6. 内联函数

将内联函数先简单提一个知识点:宏

a. 宏的复习

复习

宏的坏处:

  • 不方便调试宏。(因为预编译阶段进行了替换)
  • 导致代码可读性差,可维护性差,容易误用。
  • 没有类型安全的检查 。

宏函数:

代码举例(写一个Add的宏函数)

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

#define Add(x,y)  x + y
int main()
{
   Add(1,3) * 3;  // 1 + 3 * 3
   return 0;
}

#define Add(x,y)  (x + y)
int main()
{
   Add(1 | 3,3 & 4);  // (1 | 3 + 3 & 4) 但是 + 的优先级高于 | 和 &
   return 0;
}

#define Add(x,y)  ( (x) + (y) );
int main()
{
   Add(1,3) * 3; // ((1) + (3)); * 3
   return 0;
}

#define Add(x,y) ((x) + (y))

宏的好处:

  • 增强代码的复用性。
  • 提高性能。

替代宏的一些方式

  • 常量定义 换用const enum
  • 短小函数定义 换用内联函数

b. 内敛函数概念

inline修饰 的函数叫做内联函数,编译时 C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。(相当于借用了宏函数的一些优点)

#include<iostream>
using namespace std;
inline int Add(int x, int y)
{
	return x + y;
}
int main()
{
	cout << Add(3, 5) << endl;
	return 0;
}

c. 特性

  1. inline是一种以空间换时间 的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。

  2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同

一般建议:将函数规模较小 (即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。

  1. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。

7. 关键词 auto

auto可以自己推到对象的类型

代码举例

#include<iostream>
using namespace std;
int main()
{
	int a = 10;
	auto b = a;
	return 0;
}

注意事项:

  • auto不能直接用来声明数组

错误代码举例

#include<iostream>
using namespace std;
int main()
{
	auto arr[] = { 1,2,3,5 };
	return 0;
}
  • auto不能作为函数的参数

错误代码举例

#include<iostream>
using namespace std;
int ADD(auto x, int y)
{
	return x + y;
}
int main()
{
	int a = 4;
	int b = 5;
	ADD(a, b);
	return 0;
}
  • auto声明同时初始化

错误代码举例

#include<iostream>
using namespace std;
int main()
{
	auto b;
	return 0;
}

auto的使用规则

  1. auto与指针和引用结合起来使用用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&

代码举例

#include<iostream>
using namespace std;
int main()
{
	int a = 4;
	auto b= &a;
	auto* c = &a;
	cout << b << endl;
	cout << c << endl;
	return 0;
}

运行结果:

#include<iostream>
using namespace std;
int main()
{
	int a = 4;
	auto* c = a;
	cout << c << endl;
	return 0;
}
  1. 在同一行定义多个变量

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

代码举例

#include<iostream>
using namespace std;
int main()
{
	auto a = 4, b = 5;
	cout << a << ' '<< b << endl;
	return 0;
}

运行结果:

#include<iostream>
using namespace std;
int main()
{
	auto a = 4, b = 5.0;
	cout << a << ' '<< b << endl;
	return 0;
}
  1. 打印函数类型

代码举例(VS2022,X86)

#include<iostream>
using namespace std;
int main()
{
	auto a = 4;
	auto * b = &a;
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
	return 0;
}

运行结果:

  1. for循环遍历数组(一个个取出数组元素并且一个临时变量

代码举例1

#include<iostream>
using namespace std;
int main()
{
	int arr[] = { 1,3,4,3,5 };
	for (auto i : arr) //打印数组元素
	{
		cout << i << endl;
	}
	return 0;
}

运行结果:

取出数组的每一个元素,并且赋值给临时变量 i

代码举例2

#include<iostream>
using namespace std;
int main()
{
	int arr[] = { 1,3,4,3,5 };
	for (auto& i : arr) //修改数组元素
	{
		i *= 2;
	}
	for (auto j : arr) //打印数组元素
	{
		cout << j << endl;
	}
	return 0;
}

运行结果:

i , j 都是临时变量

for循环后的括号由冒号" :"分为两部分:第一部分是范

围内用于迭代的变量,第二部分则表示被迭代的范围

代码举例3

#include<iostream>
using namespace std;
void Print(int arr[])
{
	for (auto i : arr)
	{
		cout << arr;
	}
}
int main()
{
	int arr[] = { 1,3,4,3,5 };
	Print(arr);
	return 0;
}

无法确定 arr的范围(传过来的只是首元素的地址)

8. 指针空值 nullptr

nullptr 与 NULL 的区别

NULL实际是一个宏,在传统的C头文件(stddef.h)中

所以:它可能代表 整型 0 或者 (无类型的地址)0

而 nullptr 只有一种含义 :

(无类型的地址)0

代码举例

注意事项:

1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入

2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。

3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。

相关推荐
小王努力学编程12 分钟前
【C++篇】AVL树的实现
java·开发语言·c++
yuanbenshidiaos27 分钟前
C++-----图
开发语言·c++·算法
就一枚小白30 分钟前
UE--如何用 Python 调用 C++ 及蓝图函数
c++·python·ue5
✿ ༺ ོIT技术༻1 小时前
同步&异步日志系统:设计模式
linux·c++·设计模式
獨枭7 小时前
CMake 构建项目并整理头文件和库文件
c++·github·cmake
小王爱吃月亮糖8 小时前
C++的23种设计模式
开发语言·c++·qt·算法·设计模式·ecmascript
小哈龙10 小时前
c++ 类似与c# 线程 AutoResetEvent 和 ManualResetEvent的实现
c++·c#·多线程
yuanbenshidiaos11 小时前
C++--------------树
java·数据库·c++
海螺姑娘的小魏11 小时前
Effective C++ 条款 15:在资源管理类中提供对原始资源的访问
开发语言·c++
青青丘比特13 小时前
STL.string(下)
开发语言·c++