C++入门基础

一:为什么学习C++

下面是TIOBE2025年4月份的数据,可以看到C++仅次于Python语言。

  • C++在第四次工业革命中扮演着重要的角色
  • C++是一门高度追求效率的语言
  • C++在C语言的基础上提供了许多面向对象/模板/异常处理/STL等新的特性与内容

二:第一个C++程序

c++兼容接大多数c语言的语法。"hello,world"语句仍可正常输出,C++中需要把定义文件

代码后缀改为.cpp,vs编译器看到是.cpp就会调用C++编译器编译,linux下要用g++编译,不再是gcc

C语言:

cpp 复制代码
#include <stdio.h>

int main()
{
	printf("hello,world\n");
	return 0;
}

C++:

cpp 复制代码
#include <iostream>

using namespace std;

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

这里面出现了很多新东西不要担心,我们一一解答。

三:命名空间

3.1 namespace 的价值

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

3.2 命名空间的定义

  • 命名空间的定义需要用到namespace关键字,后面接一对花括号{},在里面定义变量/函数/类型;
  • C++中域有函数局部域,全局域,命名空间域,类域;域影响的是编译时语法查找⼀个变量/函数/类型出处(声明或定义)的逻辑,所有有了域隔离,名字冲突就解决了。局部域和全局域除了会影响编译查找逻辑,还会影响变量的生命周期,命名空间域和类域不影响变量生命周期;
  • namespace要定义在全局,也可以循环嵌套;
  • 项目中的同名namespace会被合并成一个;
  • C++标准库的namespace为std;
cpp 复制代码
namespace hb
{
	// 变量/类型/函数
}

3.3 命名空间的使用

编译器在查找一个变量时会在全局域和局部域查找,不会去命名空间查找。要使用namespace中的函数变量等有三种方式。

  • 指定命名空间查找
cpp 复制代码
#include <iostream>
int main()
{
	std::cout << "hello,world" << std::endl;
	// std 是标准库的命名空间 
	// ::操作符用于指定命名空间
	return 0;
}
  • 展开命名空间
cpp 复制代码
#include <iostream>

// 展开std中所有内容
// 注意这里并不和include一样把所有的内容都拷贝过来
using namespace std;

int main()
{

	int n; cin >> n;
	cout << n << endl;
	return 0;

}
  • 部分展开命名空间当中的变量/函数/类型
cpp 复制代码
#include <iostream>

// 将endl 和 cout 展开
using std::endl;
using std::cout;

int main()
{
	cout << 1 << endl;
	return 0;
}

四:C++的io

  • < iostream> 是Input Output Stream的缩写,是标准的输入输出库,定义了标准的输入输出对象;
  • std::cin 是istream对象,是面向窄字符的标准输入流;
  • std::cout 是ostream对象,主要面向窄字符的标准输入流;
  • std::endl 是一个函数,它的作用相当于换行+刷新缓冲区;
  • << >> 是流插入和提取操作符(C语言中也表示移位操作符);
  • C++输入输出可以自动识别变量类型(本质是通过函数重载实现的),通过重载还可以支持自定义类型输入输出;
  • cout/cin/endl等都属于C++标准库,C++标准库都放在⼀个叫std(standard)的命名空间中,所以要通过命名空间的使用方式去使用他们;
  • 一般练习可以使用 using namespace std; 但在项目中不推荐;
  • 有些编译器中 < iostream> 会自动包含<stdio.h>
cpp 复制代码
#include <iostream>

using namespace std;
int main()
{
	printf("1\n"); // 并未包含stdio.h 但是也可以运行
	return 0;
}

五:缺省参数

  • 缺省参数是声明后定义函数时为函数的参数指定一个缺省值,当没有指定实参则使用形参的缺省值。缺省分为半缺省和全缺省;
cpp 复制代码
#include <iostream>

using namespace std;

// 半缺省
int add(int a, int b = 10)
{
	return a + b;
}

// 全缺省
int sub(int a = 10, int b = 2)
{
	return a - b;
}
int main()
{
	cout << add(1) << endl; // 11
	cout << add(1, 2) << endl; // 3
	cout << sub() << endl; // 8
	cout << sub(1) << endl; // -1
	cout << sub(1, 10) << endl; // -9
	return 0;
}
  • 函数缺省值的定义需要从右向左,避免有歧义;
  • 带缺省值的函数在调用是传递实参是从左到右的,不能跳跃;
  • 函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值。

六:函数重载

  • C++⽀持在同⼀作用域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者类型不同。这样C++函数调⽤就表现出了多态行为,使用更灵活。C语⾔是不⽀持同⼀作⽤域中出现同名函数的。
cpp 复制代码
#include <iostream>

using namespace std;

int add(int a, int b, int c)
{
	return a + b + c;
}
int add(int a, int b)
{
	return a + b;
}
double add(double a, double b)
{
	return a + b;
}
int main()
{
	cout << add(1, 2, 3) << endl; // 6
	cout << add(4, 5) << endl; // 9
	cout << add(1.2, 3.14) << endl; // 4.34
	return 0;
}
  • 返回值不能作为函数重载的条件
  • 缺省参数不可以构成函数重载;
cpp 复制代码
#include <iostream>

using namespace std;

int add(int a = 10, int b = 10)
{
	return a + b;
}
int add(int a, int b)
{
	return a + b;
}

int main()
{
	cout << add(10, 10) << endl;
	return 0;
}

七:引用

7.1 引用的概念和定义

引用不是定义一个新变量,而是给变量起了个别名,编译器不会为引用变量开辟新的内存空间,它和引用变量公用一块内存空间。就像鲁迅和周树人其实是一个人。

类型& 引用别名 = 引用对象;

cpp 复制代码
#include <iostream>

using namespace std;

int main()
{
	int a = 10;
	int& b = a;

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

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

	return 0;
}

7.2 引用的特性

  • 引用在定义时必须初始化;
  • 同个变量可以有多个引用;(你不可能就一个小名吧)
  • 和java不同引用的指向并不可以修改;
cpp 复制代码
#include <iostream>

using namespace std;

int main()
{
	int a = 10;
	int b = 20;
	int& c = a;
	c = b; // 这里并不是改变引用的指向,而是一个赋值语句
	
	cout << &a << endl;
	cout << &b << endl;
	cout << &c << endl;

	return 0;
}

7.3 引用的使用

  • 引用在实践中主要用于引用传参和引用做返回值中减少拷贝太高效率和改变引用对象时同时改变被引用对象;
cpp 复制代码
// c语言版本
void swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

// c++版本
void swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}


int main()
{

	return 0;
}
  • 引用可以构成函数重载,但是调用时可能会出问题;
  • 引用传参跟指针传参的功能是类似的,引用传参相对更方便一点;
cpp 复制代码
typedef struct ListNode
{
	int val;
	struct ListNode* next;
}LTNode;

// 对于链表的插入操作,可能要改变指针本身,所以要传二级指针
void ListPushBack(LTNode** pphead, int x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	newnode->val = x;
	newnode->next = NULL;
	if (pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//...
	}
}
int main()
{
	LTNode* plist = NULL;
	ListPushBack(&plist, 1);
	return 0;
}
cpp 复制代码
// ⼀些主要⽤C代码实现版本数据结构教材中,使⽤C++引⽤替代指针传参,⽬的是简化程序,避开复
// 杂的指针,但是很多同学没学过引⽤,导致⼀头雾⽔。

typedef struct ListNode
{
	int val;
	struct ListNode* next;
}LTNode, * PNode; 

// 这里使用引用传参就可以使代码更简洁,更好理解
void ListPushBack(PNode& phead, int x)
{
	PNode newnode = (PNode)malloc(sizeof(LTNode));
	newnode->val = x;
	newnode->next = NULL;
	if (phead == NULL)
	{
		phead = newnode;
	}
	else
	{
		//...
	}
}
int main()
{
	PNode plist = NULL;
	ListPushBack(plist, 1);
	return 0;
}
  • 引用作为返回值
cpp 复制代码
#include<iostream>
#include <assert.h>

using namespace std;
typedef int STDataType;

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

void STInit(ST& rs, int n = 4)
{
	rs.a = (STDataType*)malloc(n * sizeof(STDataType));
	rs.top = 0;
	rs.capacity = n;
}

int& STTop(ST& rs)
{
	assert(rs.top > 0);
	return rs.a[rs.top - 1];
}

void STPush(ST& rs, STDataType x)
{
	if (rs.top == rs.capacity)
	{
		printf("扩容\n");
		int newcapacity = rs.capacity == 0 ? 4 : rs.capacity * 2;
		STDataType* tmp = (STDataType*)realloc(rs.a, newcapacity *
			sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		rs.a = tmp;
		rs.capacity = newcapacity;
	}
	rs.a[rs.top] = x;
	rs.top++;
}

int main()
{
	ST st;
	STInit(st, 100);
	STPush(st, 1);

	cout << STTop(st) << endl; // 1

	STTop(st) += 10; 

	cout << STTop(st) << endl; // 11

	return 0;
}
  • 补充一点:C++的越界是抽查,它会在数组的边界设置抽查位,如果被改变就报错。

7.4 const引用

  • 可以引用一个const对象,但是必须用const引用。const引用也可以引用普遍对象,因为对象权限在引用过程中可以缩小但是不可以增大;
cpp 复制代码
#include <iostream>

using namespace std;

int main()
{
	int a = 10;
	const int& _a = a; // 权限缩小

	const int b = 20;
	int& _b = b; // 权限扩大

	int& c = 20; // 右值具有常属性,这也是权限扩大的一种

	return 0;
}
  • 值得注意的是:如表达式求值;类型转换等场景下,计算的结果存储在临时变量中,而临时变量也具备常属性;
cpp 复制代码
#include <iostream>

using namespace std;

int main()
{
	double pi = 3.14;
	int& b = pi;
	const int& _b = pi;

	int a = 3;
	int& _a = a * 2;
	const int& __a = a * 2;

	return 0;
}

7.5 指针和引用的关系

C++中的指针和引用就先两个性格迥异的亲兄弟,指针是哥哥,引用是弟弟,在实践中他们相辅相成,功能有重叠,但是各自有特点,互相不可替代。

  • 语法上讲引用是一个变量的别名,并不开辟新的空间;但是指针是存储一个变量地址,要开辟空间。

  • 引用定义时必须初始化,指针建议初始化,但是语法上不是必须的。

  • 引用改变指向后不可以改变指向;指针可以。

  • 引用可以直接访问对象;但是指针必须要解引用才能访问对象。

  • sizeof中含义不同,引用结果是返回引用对象类型的大小;但是指针始终是4/8字节。

  • 指针很容易出现空指针和野指针问题,引用很少出现,引用使用起来相对安全一点。

八:inline

  • 用inline修饰的函数叫做内联函数,编译时C++编译器会在调用的地方展开内联函数,这样调用内联函数就需要建立栈帧了,就可以提高效率。

  • inline对于编译器而言只是一个建议,也就是说,你加了inline编译器也可以选择在调用的地方不展开,不同编译器关于inline什么情况展开各不相同,因为C++标准没有规定个。inline适用于频繁调用的短小函数,对于递归函数,代码相对多一些的函数,加上inline也会被编译器忽略。

  • C语言实现宏函数也会在预处理时替换展开,但是宏函数实现很复杂很容易出错的,且不方便调试,C++设计了inline目的就是替代C的宏函数。

  • vs编译器debug版本下面默认是不展开inline的,这样方便调试,debug版本想展开需要设置一下以下两个地方。

  • inline不建议声明和定义分离到两个文件,分离会导致链接错误。因为inline被展开,就没有函数地

    址,链接时会出现报错。

九:nullptr

NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:

cpp 复制代码
#ifndef NULL
	#ifdef __cplusplus
		#define NULL 0
	#else
		#define NULL ((void *)0)
	#endif
#endif
  • C++中NULL可能被定义为字面常量0,或者C中被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,本想通过f(NULL)用指针版本的f(int*)函数,但是由于NULL被定义成0,调用了f(int x),因此与程序的初衷相悖。f(void*)NULL);调用会报错。
  • C++11中引入nullptr,nullptr是一个特殊的关键字,nullptr是一种特殊类型的字面量,它可以转换成任意其他类型的指针类型。使用nullptr定义空指针可以避免类型转换的问题因为nullptr只能被隐式地转换为指针类型,而不能被转换为整数类型。
cpp 复制代码
#include<iostream>
using namespace std;
void f(int x)
{
	cout << "f(int x)" << endl;
}
void f(int* ptr)
{
	cout << "f(int* ptr)" << endl;
}
int main()
{
		f(0);
		// 本想通过f(NULL)调⽤指针版本的f(int*)函数,但是由于NULL被定义成0,调⽤了f(int x),因此与程序的初衷相悖。
		
		 f(NULL);
		 f((int*)NULL);
	
		 // 编译报错:error C2665: "f": 2 个重载中没有⼀个可以转换所有参数类型
		 // f((void*)NULL);
		
		 f(nullptr);
	
		 return 0;
	
}
相关推荐
菜就多练吧15 分钟前
JVM 内存分布详解
java·开发语言·jvm
搬砖工程师Cola1 小时前
<C#>.NET WebAPI 的 FromBody ,FromForm ,FromServices等详细解释
开发语言·c#·.net
李长渊哦3 小时前
深入理解 JavaScript 中的全局对象与 JSON 序列化
开发语言·javascript·json
Non importa3 小时前
【初阶数据结构】树——二叉树(上)
c语言·数据结构·学习·算法
若水晴空初如梦3 小时前
QT聊天项目DAY06
开发语言·qt
Rousson4 小时前
硬件学习笔记--57 MCU相关资源介绍
笔记·单片机·mcu·学习
刘卜卜&嵌入式4 小时前
C++_设计模式_观察者模式(Observer Pattern)
c++·观察者模式·设计模式
XINVRY-FPGA5 小时前
XCZU7EG‑L1FFVC1156I 赛灵思XilinxFPGA ZynqUltraScale+ MPSoC EG
c++·嵌入式硬件·阿里云·fpga开发·云计算·fpga·pcb工艺
HtwHUAT5 小时前
实验四 Java图形界面与事件处理
开发语言·前端·python