当前位置: 首页 > news >正文

C++ 初识

一、第一个 C++ 代码

#include <iostream>using namespace std;int main()
{int x; cin >> x;cout << "hello world" << endl;return 0;
}
// std C++ 标准库的命名空间
// namespace 命名空间
// cin cout 输入输出流对象
// >> << 流插入 流提取操作符
// endl endline 换行 + 刷新缓冲区

C++ 的很多东西是从 C 语言中诞生的,其中有很多是的对 C 语言面向对象的完善,即其本身缺陷的填补。上面的陌生名词会在下面做一些简单的介绍。

二、namespace 命名空间

(一)、namespace 的作用

#include <iostream>
#include <stdlib.h>
int rand = 10;
int main()
{std::cout << rand << std::endl;return 0;
}
// 这段代码运行会报错,rand 重定义
// 原因是 stdlib.h 中存在一个 rand 函数,rand 被重定义了

在 C 语言阶段,会存在命名冲突的问题,如果自己写好的程序后续包含一个第三方库,出现一堆重定义问题会很红温,namespace 很好的解决了这样的问题。

  • namespace 的创建类似于结构体,被 {} 包含,区别是最后没有 ; 。namespace 的内部可以定义 函数,变量,类对象等。namespace 支持嵌套定义,但是只能定义在全局。
#include <iostream>
#include <vector>
const int MAXN = 15;
// 可以看到 rand 此时并不会报错
// 同样定义了两个 get_dist 函数,也不会出现命名冲突
// 下面的数组也是同样的情况,dist 和 st 在两个域里面不会冲突
namespace hans
{int rand = 10;// 命名空间的嵌套定义namespace A{void get_dist(int s){// djkstd::vector<std::pair<int, int>> edges[MAXN];int dist[MAXN];bool st[MAXN];int n, m; std::cin >> n >> m;for (int i = 1; i <= m; i++){int u, v, w; std::cin >> u >> v >> w;edges[u].push_back({v, w});}for (int i = 0; i <= n; i++) dist[i] = 0x3f3f3f3f;dist[s] = 0;for(int i = 1; i < n; i++){int u = 0;for (int j = 1; j <= n; j++){if (!st[j] && dist[j] < dist[u]) u = j;}st[u] = true;for (auto& [v, w] : edges[u]){if (dist[v] > dist[u] + w){dist[v] = dist[u] + w;}}}for (int i = 1; i <= n; i++) std::cout << dist[i] << " ";std::cout << std::endl;}}namespace B{void get_dist(int s){// bfstd::vector<std::pair<int, int>> edges[MAXN];int dist[MAXN];bool st[MAXN];int n, m; std::cin >> n >> m;for (int i = 1; i <= m; i++){int u, v, w; std::cin >> u >> v >> w;edges[u].push_back({ v, w });}for (int i = 0; i <= n; i++) dist[i] = 0x3f3f3f3f;dist[s] = 0;bool flag;for (int i = 1; i < n; i++){flag = true;for (int u = 1; u <= n; u++){for (auto& [v, w] : edges[u]){if (dist[v] > dist[u] + w){dist[v] = dist[u] + w;flag = false;}}}if (flag) break;}}	}}int main()
{return 0;
}
  • namespace 本质上是定义一个单独的域,不同与局部域和全局域,命名空间域不会更改变量的声明周期。他会影响查找规则:
    • 一般情况下,编译器会先从局部域找,然后去全局域找,并不会去命名空间域查找。
    • 去命名空间里面查找需要用到 :: 域限定操作符,指定命名空间域查找。
#include <iostream>namespace hans
{int x = 3;
}
int x = 2;
int main()
{int x = 1;std::cout << x << std::endl; // 默认局部域查找std::cout << ::x << std::endl; // 全局域查找std::cout << hans::x << std::endl; // 指定命名空间查找return 0;
}
  • std 是 C++ 标准库的命名空间。

(二)、namespace 的使用

  • using 全部展开命名空间,不同于 include 的编译时的全部拷贝,using 更改的是查找规则,让编译器回去到相应的命名空间查找。
  • using std:: cin 类似于这样的是命名空间中展开部分的名字。
  • 限定命名空间域查询,这种一般工程中经常用到。
#include <iostream>
// 1
using namespace std; 
// 2
using std::cin;
using std::cout;
using std::endl;int main()
{int x;// 3std::cin >> x;return 0;
}

三、C++ 的输入和输出

  • iostream 是 C++ 输入输出流的头文件,o 代表 output,i 代表 input,定义了输入输出流对象。
  • iostream 在某些编译器下面会包含 C 的输入输出,即printf 和 scanf
  • cin 是标准输入流对象,主要面向窄字符的输入。
  • cout 是标准输出流对象,主要面向窄字符的输出。
  • endl 的作用类似于 \n ,是一个函数,但是它会额外刷新缓冲区。
  • << >> 为流插入 和 流提取 运算符,区别于移位运算符,本质因为函数重载。
  • cin 和 cout 优于 C 的标准输入输出的主要原因是它们可以通过重载输入输出自定义类型。
  • cin cout endl 性能上较慢,这其中涉及到兼顾 C 和 缓冲区的问题。竞赛上一般会把同步流关闭,但是性能还是稍微慢于printf 和 scanf。
#include <iostream>using namespace std;int main()
{cin.tie(0), cout.tie(0);ios::sync_with_stdio(false);return 0;
}

四、缺省参数

  • 缺省参数即在函数的参数部分给一个缺省值,也叫默认参数,缺省参数的函数在调用的过程中,有传的参数就用传的参数,否则为缺省值。
  • 缺省参数能构成函数重载,但是调用有歧义。
  • 缺省参数只能从右到左给,不支持跳跃。
  • 缺省参数函数的调用只能从左到右依次赋值,不支持跳跃。
  • 函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值。
#include <iostream>using namespace std;void f1(int a, int b = 1, int c = 2) // 半缺省
{cout << a << " " << b << " " << c << endl;
}
void f2(int a = 0, int b = 1, int c = 2) // 全缺省
{cout << a << " " << b << " " << c << endl;
}
void f3(int a = 0, int b, int c) // 不支持这样写
{cout << a << " " << b << " " << c << endl;
}int main()
{f1(1, 2, 3);f1(1);f2();f2(1, 2, 3);f2(1, , 1); // 不支持这样传return 0;
}
  • 缺省参数的应用场景很多,比如在栈初始化的时候,当我们知道需要插入元素的个数时,就可以指定初始空间的大小,从而避免多次异地扩容带来的时间开销。

五、函数重载

C 语言不允许一个域中出现同名函数,本贾尼博士觉得不是很合理,允许同名函数在构成构成函数重载时可以同时存在,下面是构成函数重载的条件

  • 参数个数不同
  • 参数类型不同

特别的,下面两个也会构成函数重载,但是调用会有歧义

  • 缺省参数
  • 引用(下面会说到)

但是函数的返回值不同并不会构成函数重载。

#include <iostream>
// 1 和 2,1 和 4 由于参数类型不同否成函数重载
// 1 和 3 由于参数个数不同构成函数重载
// 1 和 5 由于缺省参数构成函数重载
// 1 和 6 由于引用构成函数重载
// 1 和 7 返回类型不同,但不构成函数重载,因为调用也会有歧义
// 上述 1 和 5 即 1 和 6,本质是因为参数类型不同构成函数重载,但是会存在调用歧义,编译器不清楚具体时调用那个函数
// 可以显式类型转换解决这个问题
// 为了避免这种情况的出现会用模板特化,后面会说
// 1
void swap(int a, int b)
{}
// 2
void swap(double a, double b)
{}
// 3
void swap(int a, int b, int c)
{}
// 4
void swap(int* pa, int* pb)
{}
// 5
void swap(int a = 10, int b = 10)
{}
// 6
void swap(int& a, int& b)
{}
// 7
int swap(int a, int b)
{}
int main()
{int a = 2, b = 10;double c = 1, d = 2;swap(a, b);swap(c, d);return 0;
}

六、引用

(一)、引用的概念和定义

引用是变量的一个别名,a 是 b 的引用,即 a 是 b 的别名,本质上是一个东西。

引用的定义:类型& 引用别名 = 引用变量

#include <iostream>int main()
{int a = 10;int& b = a; // b 是 a 的别名// 两者的地址是一样的std::cout << &a << std::endl;std::cout << &b << std::endl;std::cout << std::endl;// 支持连续的引用int& c = b;int& d = c;std::cout << &a << std::endl;std::cout << &b << std::endl;std::cout << &c << std::endl;std::cout << &d << std::endl;std::cout << std::endl;// 由于引用是起别名,对别名的修改会直接影响到原变量int e = 20;d = e; // 这里是把 20 赋值给 d,并不是改变引用的指向std::cout << a << std::endl;std::cout << b << std::endl;std::cout << c << std::endl;std::cout << d << std::endl;std::cout << std::endl;// 对指针的引用int* pa = &a;int*& rpa = pa;std::cout << &pa << std::endl;std::cout << &rpa << std::endl;return 0;
}

(二)、引用的特性

  • 引用创建后不能改变指向
  • 引用创建时必须指定引用对象
  • 引用可以连续引用

(三)、引用的使用

  • 引用作为函数参数
  • 引用作为函数返回值

这样做的好处:

  • 可以减少无需的拷贝的额外时间开销,特别是后面深拷贝的时候
  • 引用可以改变引用对象的值
#include <iostream>using namespace std;struct node
{int a[100];
};//void fun(node t)
//{
//	// ...
//}
//void fun(node& t)
//{
//	// ... 
//}void swap1(int a, int b)
{a = a ^ b;b = a ^ b;a = a ^ b;
}
void swap2(int& a, int& b)
{a = a ^ b;b = a ^ b;a = a ^ b;
}
int main()
{node t;//fun(t);int a = 1, b = 2;swap1(a, b);cout << a << " " << b << endl;swap2(a, b);cout << a << " " << b << endl;return 0;
}
#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;
}
// 栈顶
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& STTop(ST& rs)
{assert(rs.top > 0);return rs.a[rs.top];
}
//int STTop(ST& rs)
//{
//	assert(rs.top > 0);
//	return rs.a[rs.top];
//}
int main()
{// 调⽤全局的ST st1;STInit(st1);STPush(st1, 1);STPush(st1, 2);cout << STTop(st1) << endl;STTop(st1) += 10;cout << STTop(st1) << endl;return 0;
}
  • 需要注意的是,传引用返回一定要避免野引用
#include <iostream>using namespace std;
typedef struct node
{int a[10];int size;
}ST;//// 返回堆上的空间
//int STAt(ST v, int i)
//{
//	// ...
//	return v.a[i];
//}
//
//int& STAt(ST v, int i)
//{
//	// ...
//	return v.a[i];
//}// 返回栈帧内的空间
// 函数销毁后栈帧也被销毁,空间还给操作系统
// 这时候就造成了野引用
int& a()
{int ret = 0;return ret;
}int& b()
{int ret = 1;return ret;
}int main()
{// 这种就像租完酒店自己配了一把钥匙// 当退房之后还可以继续进入酒店// 当有人在酒店里面放东西,你同样可以看到// 这里的 a b 中定义的 ret 本质上占用一块栈帧// 因此 ret 被重新定义后,x 引用的空间的值也就改变// 当然这也取决于编译器的行为,有的编译器会在栈帧销毁时把空间设置为随机值// 这样就会造成很多不必要的麻烦int& x = a();cout << x << endl;b();cout << x << endl;return 0;
}

某些时候引用也会简化代码,提高代码的简洁性,但是需要注意的是,引用并不是 C 语言的内容,这样写需要用 C++ 的编译器编译。

#include <iostream>typedef struct ListNode
{int val;struct ListNode* next;
}LTNode, *PNode;// 以往单链表的尾插操作需要传二级指针
void LTpush_back(LTNode** pphead, int x)
// 但是下面两种参数的设计同样可以达到效果
// 引用的是指针本身,可以对其指向进行修改
//void LTpush_back(LTNode*& phead, int x)
//void LTpush_back(PNode& pphead, int x)
{PNode newnode = (PNode)malloc(sizeof(LTNode));// ...newnode->next = NULL;newnode->val = x;if (*pphead == NULL) // 链表为空{*pphead = newnode;}else{// ...}
}
int main()
{PNode plist = NULL;LTpush_back(&plist, 1);//LTpush_back(plist, 1);return 0;
}

(四)、const 引用

  • 类似于指针,引用的访问访问权限同样不可放大只可缩小
  • 用 const & 可以引用 const 变量、常量、临时变量
  • 所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象,C++中把这个未命名对象叫做临时对象。
#include <iostream>using namespace std;int main()
{int x = 1;// 权限的缩小const int& rx1 = x;int& rx1 = x;const int y = 2;const int& ry1 = y;// 权限的放大//int& ry2 = y;  // 需要注意的是,访问权限针对于指针和引用// 不同变量不涉及权限问题int z = y; // 仅仅是把 y 赋值给 z,z 的改变不影响 yconst int& ra = 10; // 常量const int& rb = 10 * x; // 临时对象return 0;
}

(五)、引用与指针的关系

指针变量是可以独立存在的变量,而引用并不独立,引用本身具备指针改变指向对象的功能,但是在一些需要修改本身指向的时候不能代替指针变量独立存在。

  • 指针是一个变量存储地址,需要开空间;引用是变量的别名,并没有额外创建变量,不需要开空间。
  • 引用定义时必须初始化;指针定义时可以不初始化。
  • 引用不可以改变指向;指针可以改变指向。
  • 引用可以直接访问指向对象;指针需要解引用才可以访问指向对象。
  • sizeof 的结果对于引用来讲是指向对象的大小;对于指针则为 4 或 8 字节。
  • 指针很容易出现空指针和野指针的问题,引用很少出现,相对来说安全一点。

七、内联 / inline

  • 用 inline 修饰的函数叫做内联函数,调用内联函数时,编译器会自动在调用位置展开,不需要建立函数栈帧,可以提高运行效率
  • inline 函数的是否展开具体要看编译器的实现,对于短小的函数,编译器会将其展开,但对于码量很大,或者一些递归函数来说,编译器不会将其展
  • C 语言实现宏函数也会在预处理时替换展开,但是宏函数实现很复杂很容易出错的,且不方便调试,C++设计了 inline 的目的就是替代宏函数
  • inline不建议声明和定义分离到两个文件,分离会导致链接错误。因为 inline 被展开,就没有函数地址,链接时会出现报错。
#include <iostream>using namespace std;inline int read()
{int ret = 0, flag = 1;char ch = getchar();while (ch < '0' || ch > '9'){if (ch == '-') flag = -1;ch = getchar();}while (ch >= '0' && ch <= '9'){ret = ret * 10 + ch - '0';ch = getchar();}return ret * flag;
}inline void write(int x)
{if (x < 0){putchar('-');x = -x;}if (x > 9) write(x / 10);putchar(x % 10 + '0');
}int main()
{int x = read();write(x);return 0;
}

八、nullptr

#ifndef NULL#ifdef __cplusplus#define NULL 0#else#define NULL ((void *)0)#endif
#endif
  • C++11中引入 nullptr,nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字面量,它可以转换成任意其他类型的指针类型。使用 nullptr 定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,而不能被转换为整数类型。
#include <iostream>using namespace std;void f(int a)
{cout << "f(int a)" << endl;
}void f(int* a)
{cout << "f(int* a)" << endl;
}int main()
{// NULL 会被解释成整形f(NULL); // 这样就没有歧义了f(nullptr);return 0;
}

文章转载自:

http://fzIpx9oI.dghLr.cn
http://BXzGmbth.dghLr.cn
http://AMGnX8sJ.dghLr.cn
http://Hb410VQD.dghLr.cn
http://lXNFkViD.dghLr.cn
http://5Ykt0G4p.dghLr.cn
http://CJHQNmQw.dghLr.cn
http://rzOUVNDf.dghLr.cn
http://QhkN3m9T.dghLr.cn
http://WdEHTFdy.dghLr.cn
http://uE3cCcLF.dghLr.cn
http://21LTq9xK.dghLr.cn
http://yuOz1MP3.dghLr.cn
http://BFwlgqZv.dghLr.cn
http://Ewsmzvzp.dghLr.cn
http://VEPthP2H.dghLr.cn
http://tvTHNyNP.dghLr.cn
http://cR5Roh8z.dghLr.cn
http://um76GJ4g.dghLr.cn
http://YrpgDPtM.dghLr.cn
http://OXdDuHuT.dghLr.cn
http://Jq7fCVif.dghLr.cn
http://84M1KY1R.dghLr.cn
http://37d02gg3.dghLr.cn
http://YZpJUsFu.dghLr.cn
http://m3u7xKIb.dghLr.cn
http://7EQZa95K.dghLr.cn
http://SYVEVaRO.dghLr.cn
http://WrtHmQWn.dghLr.cn
http://lG3yhPfg.dghLr.cn
http://www.dtcms.com/a/387601.html

相关文章:

  • 从零实现 Qiankun 微前端:基座应用控制子应用路由与信息交互
  • 云函数(Serverless)深度解读
  • 设计模式概述
  • 基于 TCP 协议的 C++ 计算器项目实现:从网络通信到协议封装
  • 【分布式技术】深入理解AMQP(高级消息队列协议)
  • 海外短剧分销系统开发:技术栈选型与核心模块实现指南
  • 每日前端宝藏库 | Toastify.js ✨
  • Nuxt3:自动导入渲染模式服务器引擎生产部署模块化
  • 打造高效对账单管理组件:Vue3 + Element Plus 实现客户账单与单据选择
  • 第二章 Arm C1-Premium Core技术架构
  • Bartender 6 多功能菜单栏管理(Mac)
  • 嵌入式科普(38)C语言预编译X-Macros深度分析和实际项目代码分享
  • Docker compose 与 docker swarm 的区别
  • 【嵌入式硬件实例】-555定时器实现水位检测
  • AbMole小课堂丨R-spondin-1(RSPO1):高活性Wnt通路激活剂,如何在多种类器官/干细胞培养中发挥重要功能
  • 【C语言代码】打印九九乘法口诀表
  • vue3和element plus, node和express实现大文件上传, 分片上传,断点续传完整开发代码
  • electron-egg使用ThinkPHP项目指南
  • 温州工业自动化科技工厂如何实现1台服务器10个研发设计同时用
  • 如何用PM2托管静态文件
  • Java程序设计:基本数据类型
  • 在k8s环境下部署kanboard项目管理平台
  • 为什么 MySQL utf8 存不下 Emoji?utf8mb4 实战演示
  • 2025 年 PHP 常见面试题整理以及对应答案和代码示例
  • (二十五)、在 k8s 中部署证书,为网站增加https安全认证
  • 风机巡检目前有什么新技术?
  • 震坤行工业超市开放平台接口实战:工业品精准检索与详情解析全方案
  • 河南萌新联赛2025第(八)场:南阳理工学院
  • docker回收和mysql备份导入导致数据丢失恢复---惜分飞
  • 「Memene 摸鱼日报 2025.9.17」上海张江人工智能创新小镇正式启动,华为 DCP 技术获网络顶会奖项