少年不惧岁月长,彼方尚有荣光在。
前言
这是我自己学习C++的第一篇博客总结。后期我会继续把C++学习笔记开源至博客上。
C++的兼容性
1. C++兼容绝大多数C语言的语法,因此只需要把 .c 后缀改为 .cpp 后缀即可。
2. VS编辑器看到是 .cpp 后缀就会调用C++的编译器进行编译。
#include <stdio.h>
int main()
{
printf("hello world\n");
return 0;
}
//实际上应该这样写
#include <iostream>
using namespace std;
int main()
{
cout << "hello world" << endl;
return 0;
}
命名空间
namespace关键字
1. 在 C/C++ 中,变量、函数和类都是 大量存在的 。这些变量、函数和类的名称,如果都存在于 全局作用域 中,则可能会导致 很多冲突 。2. 使用 命名空间 的目的是 对标识符的名称进行本地化 ,避免命名冲突或名字污染。3. 定义命名空间 时,需要使用到 namespace关键字 ,后面跟命名空间的名字,然后接一对 { } 即可, { } 中即为 命名空间的成员 。命名空间中可以定义 变量、函数、类型 等。4. namespace 本质是定义一个域,创建一个命名空间,这个域跟其他的域相互独立。5. 不同的域可以定义同名变量,但同一个域不能定义同名变量。6. 命名空间域 和 类域 只能在 全局域中定义 ,原本就是为了在全局域中进行隔离。所以里面的变量、函数的 生命周期都是全局的 。7. 命名空间域里面可以继续 嵌套命名空间域 ,使用里面内容时需要多用几个 作用域限定符 。8. 多个文件 中可以定义 同名namespace ,它们会 默认合并到⼀起 ,就像 同一个namespace 一样。9. C++标准库 都放在一个叫 std(standard)的命名空间 中。10. 域 分为 局部域 、 全局域 、 名命空间域 和 类域 。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
namespace hsy
{
int rand = 10;//命名空间域相当于在全局域中划出一片区域,形成独立的命名空间域
}
int main()
{
printf("%d\n", hsy::rand);//10
return 0;
}
x//局部变量的x
::x//全局变量的x
hsy::x//命名空间hsy的x
:://域作用限定符
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
namespace hsy
{
namespace yhy
{
int rand = 2024;
}
namespace sbd
{
int rand = 2023;
}
}
//命名空间域里面可以继续嵌套命名空间域。
int main()
{
printf("%d\n", hsy::yhy::rand);//2024
printf("%d\n", hsy::sbd::rand);//2023
return 0;
}
命名空间的使用
1. 编译器查找一个变量的声明、定义时,默认只会在局部或者全局查找,不会到命名空间里面去查找。
2. 所以我们要使用命名空间中定义的变量、函数,有三种方式:
- 指定命名空间访问。
using将命名空间中某个成员展开。 using展开命名空间中全部成员。
#include<stdio.h>
namespace hsy
{
int a = 0;
int b = 1;
}
int main()
{
printf("%d\n", a);// 编译报错:error C2065: “a”: 未声明的标识符
return 0;
}
int main()
{
printf("%d\n", hsy::a);// 指定命名空间访问
return 0;
}
using hsy::b;// using将命名空间中某个成员展开,相当于把命名空间域hsy里面的东西暴露在全局域中
int main()
{
printf("%d\n", hsy::b);
printf("%d\n", b);//效果相同
return 0;
}
using namespce hsy;// 展开命名空间中全部成员
int main()
{
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
C++的输入输出
1. <iostream>头文件,是Input Output Stream的缩写,包括了标准的输入、输出流库,定义了标准的输入、输出对象。
2. <<是''流插入''运算符,>>是''流提取''运算符。
3. std::cin是istream类的对象,标准输入流。
4. std::cout是ostream类的对象,标准输出流。
5. std::endl是一个函数,相当于插入一个换行字符加刷新缓冲区。
6. 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动指定格式,C++的输入输出可以自动识别变量类型。
7. cout/cin/endl等都属于C++标准库,C++标准库都放在一个叫std(standard)的命名空间域中,所以要通过命名空间的使用方式去用他们。
8. 一般日常练习中我们可以using namespace std;。
#include <iostream>
using namespace std;
int main()
{
int a = 0;
cout << a << " " << 'b' << endl;// 可以⾃动识别变量的类型
std::cout << a << " " << 'b' << std::endl;// 可以⾃动识别变量的类型
return 0;
}
//0 b
//0 b
缺省参数
1. 缺省参数 是 声明 或 定义 函数时,为 函数的形式参数 指定 一 个 缺省值 。2. 在调用该函数时,如果没有指定实参,则采用形参的缺省值,否则使用指定的实参。3. 缺省参数 分为 全缺省 和 半缺省参数 ;全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。4. C++ 规定半缺省参数必须 从右往左依次连续缺省 ,不能间隔跳跃给缺省值。5. 函数声明和定义分离时 ,缺省参数不能在函数声明和定义中同时出现,规定必须 函数声明给缺省值 。
#include <iostream>
using namespace std;
void Add(int a = 10, int b = 5)
{
cout<<(a + b)<<endl;
}
void Func1(int a = 10, int b = 20, int c = 30)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
int main()
{
Add(1,2);//有传实参时候,使用实参值
Add();//没有传实参时候,使用参数默认值
Func1();
Func1(1);
Func1(1,2);
Func1(1,2,3);
Func1(,2,);//格式错误,不能跳跃
}
#include <iostream>
#include <assert.h>
using namespace std;
typedef int StackDataType;
typedef struct Stack
{
StackDataType* arr;
int top;
int capacity;
}Stack;
void StackInit(Stack* ps, int n = 4);// 如果函数声明和定义同时存在时,则在函数声明时给定缺省值
void StackInit(Stack* ps, int n)
// 缺省参数不能声明和定义同时给,如果函数声明时候给了,则函数定义时候就不能给。
{
assert(ps);
ps->arr = (StackDataType*)malloc(n * sizeof(StackDataType));
ps->top = 0;
ps->capacity = n;//如果没传入n的值,那么使用缺省参数为4
}
int main()
{
Stack s1;
StackInit(&s1);
Stack s2;
StackInit(&s2, 1000);// 确定知道要插⼊1000个数据,初始化时⼀把开好,避免扩容
return 0;
}
函数重载
1. C语言不支持同一作用域中出现同名函数的。
2. C++支持在同一作用域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者参数类型不同。
3. 函数重载是通过函数参数类型、个数的不同来加以区分的。
#include<iostream>
using namespace std;
// 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;
}
// 2、参数个数不同
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
// 3、参数类型顺序不同
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;
}
// 返回值不同不能作为重载条件,因为调⽤时⽆法通过参数区分
void fxx()
{}
int fxx()
{
return 0;
}
// 下⾯两个函数构成重载
// 但是f()但是调⽤时,会报错,存在歧义,编译器不知道调⽤谁,可能是第一个函数没有参数,可能是第二个函数缺省参数。
void f1()
{
cout << "f()" << endl;
}
void f1(int a = 10)
{
cout << "f(int a)" << endl;
}
引用
引用的概念
1. 引用 不是新定义 一 个变量,而是 给已存在的变量取别名 ,编译器不会为引用变量开辟内存空间, 它和它引用的变量 共用同一块内存空间 。2. 引用格式: 类型& 引用别名=引用对象 。3. 区别于 typedef关键字 , 用于 给类型取别名 ,而 引用 多用于 给变量取别名 。4. 引用 可以用来代替往函数里面 传地址 的操作。
#include<iostream>
using namespace std;
int main()
{
int a = 0;
// 引⽤:b和c是a的别名
int& b = a;
int& c = a;
// 也可以给别名b取别名,d相当于还是a的别名
int& d = b;
++d;
// 这⾥取地址我们看到是⼀样的
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
cout << &d << endl;
return 0;
}
#include <iostream>
using namespace std;
void swap1(int x,int y)
{
int mid;
mid = y;
y = x;
x = mid;
}
void swap2(int& rx, int& ry)
{
int mid;
mid = ry;
ry = rx;
rx = mid;
}
int main()
{
int a = 10;
int b = 20;
swap1(a, b);//没交换
cout << a << " " << b << endl;
swap2(a, b);//交换了
cout << a << " " << b << endl;
return 0;
}
引用的特性
1. 引用在定义时必须初始化,指针在定义时候不一定要初始化。
2. 一个变量可以有多个引用。
3. 引用一旦引用一个实体,再不能引用其他实体。
4. 由第三条特性可知:引用不能替代指针,引用是用来辅助指针的。
int a=20,b=30;
int& pa=a;//引用pa是变量a的别名
pa=10;//把10赋值给pa,就是把10赋值给a
pa=b;//这里不是把引用pa重新设定为变量b的别名,而是把b的值赋值给pa,即把b的值赋值给a
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int& b = a;//不能这样: int& b,引用定义是必须初始化;
int& c = b;
cout << a << " " << b << " " << c << endl;//10 10 10
int e = 20;
b = e;//把e的值赋值给b(a)(c)
cout << a << " " << b << " " << c << " " << e << endl;//20 20 20 20
return 0;
}
引用的使用
1. 引用主要是于引用传参,改变引用对象(rx,ry)时同时改变被引用对象(x,y)。
2. 引用传参跟指针传参功能是类似的,引用传参相对更方便⼀些。
#include <iostream>
using namespace std;
void Swap(int& rx, int& ry)
{
int tmp = rx;
rx = ry;
ry = tmp;
}
int main()
{
int x = 0, y = 1;
cout << x << " " << y << endl;
Swap(x, y);//改变引用对象(rx,ry)的同时,改变被引用对象(x,y)。
cout << x << " " << y << endl;
return 0;
}
const修饰与引用
1. 对于引用来说:权限不能放大,但是可以缩小。
2. 对变量进行赋值时,在计算中间值和类型转换中会产生临时量对象,而C++规定临时量对象具有常属性,如果再取引用,就会触发了权限放大,必须要用const修饰才可以。
3. 在自定义函数传参时候,形参如果使用引用传参,则建议使用const修饰形参,这样传入的实参形式就更多样。
#include <iostream>
using namespace std;
int main()
{
const int a = 10;
int& ra = a;//报错,const修饰变量,权限不能放大,变量a已经被const修饰,那么变量a的引用ra也必须被const修饰
const int& ra = a;//正确
int b = 20;
const int& rb = b;//不会报错,const修饰变量,权限可以缩小,变量b没有被const修饰,那么变量b的引用rb也可以被const修饰
int c = 30;
int& rc = c * 3;//报错,赋值过程中出现了计算中间值,中间值是临时量对象,默认已经用const修饰,权限不能放大,那么变量(3*c)的引用rc也必须被const修饰
const int& rc = c * 3;//正确
return 0;
}
void Add(const int& ra)
Add(3);
Add(3*5);
Add(3.12);
指针与引用
1. 语法概念上, 引用 是一个变量的 取别名 , 不开空间 ; 指针 是存储一个变量地址, 要开空间 。2. 引用在定义时必须初始化;指针建议初始化,但是语法上不是必须的。3. 引用在初始化时,引用一个对象后,就不能再引用其他对象;而指针可以在不断地改变指向对象。4. 引用可以直接访问指向对象;指针需要解引用才是访问指向对象。5. sizeof() 中含义不同,引用结果为引用类型的大小;但指针始终是地址空间所占字节个数。6. 指针很容易出现空指针和野指针的问题;引用很少出现,引用使用起来相对更安全一些。
inline内联修饰
1. 用inline修饰的函数叫做内联函数,编译时C++编译器会在调用的地方直接展开内联函数,这样调用内联函数时就不需要建立栈帧了,可以提高效率。
2. inline对于编译器而言只是一个建议,inline适用于频繁调用的短小函数,对于代码相对多一些的函数,使用inline修饰也会被编译器忽略。
3. C语言中宏的本质就是在编译的时候进行替换,C语言实现宏函数也会在预处理时替换展开,但是宏函数实现很复杂很容易出错的,且不方便调试。C++设计了inline目的就是替代C语言的宏函数。
4. inline修饰函数时,声明和定义不能分离到两个文件,因为分离会导致链接错误。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#define Add(x,y) ((x)+(y))
//后面不能加分号
//必须加外面的括号
//必须加里面的括号
using namespace std;
int main()
{
int ret = Add(1, 2);
cout << ret << endl;
return 0;
}
inline int Add(int x, int y)
{
int ret = x + y;
return ret;
}
nullptr空指针
1. 程序员通常会用0或NULL来表示一个空指针。但是这种方式存在一些潜在的问题,主要是因为0和NULL实际上是整形常量,在某些情况下可能会导致意外的类型转换。
2. C++11中引入nullptr,nullptr是一个特殊的关键字,它可以转换成任意其他类型的指针类型。使用nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,而不能被转换为整数类型。
3. nullptr提供了一种更安全、更清晰的方式来表示空指针,有助于减少错误并提高代码的可读性和安全性。
void f(int)
{
std::cout << "Integer version" << std::endl;
}
void f(char*)
{
std::cout << "Pointer version" << std::endl;
}
int main()
{
f(0); // 可能调用f(int),也可能调用f(char*),取决于编译器
f(nullptr); // 明确调用f(char*),因为nullptr明确指向一个指针
}
致谢
感谢您花时间阅读这篇文章!如果您对本文有任何疑问、建议或是想要分享您的看法,请不要犹豫,在评论区留下您的宝贵意见。每一次互动都是我前进的动力,您的支持是我最大的鼓励。期待与您的交流,让我们共同成长,探索技术世界的无限可能!