少年不惧岁月长,彼方尚有荣光在。
前言
这是我自己学习C++的第一篇博客总结。后期我会继续把C++学习笔记开源至博客上。
C++的兼容性
1. C++兼容绝大多数C语言的语法,因此只需要把 .c 后缀文件改为 .cpp 即可。
2. VS编译器看到是.cpp就会调用C++编译器编译。
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>int main(){printf("hello world\n");return 0;}//hello world//实际上应该这样写#include <iostream>using namespace std;//将标准库展开后,调用标准库的函数时,就可以不需要作用域限定符。int main(){cout << "hello world" << endl;return 0;}//hello world
命名空间
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>#include <stdlib.h>namespace hsy{int rand = 10;//命名空间域相当于在全局域中划出一片区域,形成独立的命名空间域}int main(){printf("%p\n", rand);//00007FFD2F024D50printf("%d\n", hsy::rand);//10 return 0;}
x//局部变量的x::x//全局变量的xhsy::x//命名空间hsy的x:://域作用限定符
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>namespace hsy{namespace yhy{int rand = 2024;}namespace sbd{int rand = 2023;}}//命名空间域里面可以继续嵌套命名空间域。int main(){printf("%p\n", rand);//00007FFD2F024D50printf("%d\n", hsy::yhy::rand);//2024printf("%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, );//格式错误,不能跳跃}
// Stack.h#include <iostream>#include <assert.h>using namespace std;typedef int STDataType;typedef struct Stack{STDataType* a;int top;int capacity;}ST;void STInit(ST* ps, int n = 4);// 如果函数声明和定义同时存在时,在函数声明时给定缺省值// Stack.cpp#include"Stack.h"void STInit(ST* ps, int n)// 缺省参数不能声明和定义同时给,如果函数声明时候给了,则函数定义时候就不能给。{assert(ps && n > 0);ps->a = (STDataType*)malloc(n * sizeof(STDataType));ps->top = 0;ps->capacity = n;}// test.cpp#include"Stack.h"int main(){ST s1;STInit(&s1);// 确定知道要插⼊1000个数据,初始化时⼀把开好,避免扩容ST s2;STInit(&s2, 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, 它和它引用的变量共用同⼀块内存空间。 2. 引用格式:类型& 引用别名 = 引用对象 。 3. 区别于 typedef 关键字,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. 由第三条特性可知:引用不能替代指针。(引用是用来辅助指针的)
#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 10int e = 20;b = e;//把e的值赋值给b(a)(c)cout << a << " " << b << " " << c << " " << e << endl;//20 20 20 20return 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. const引用时候,权限不能放大,但是可以缩小。
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引用 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修饰函数时不建议声明和定义分离到两个文件,分离会导致链接错误。因为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明确指向一个指针}
致谢
感谢您花时间阅读这篇文章!如果您对本文有任何疑问、建议或是想要分享您的看法,请不要犹豫,在评论区留下您的宝贵意见。每一次互动都是我前进的动力,您的支持是我最大的鼓励。期待与您的交流,让我们共同成长,探索技术世界的无限可能!
结语
我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=1j3zlwsmeln57