C++入门(零C++基础也能看懂)
namespace后面跟命名空间的名字,再加上一对{},就是命名空间的格式了。{}中即为命名空间的成员,命名空间中可以定义变量、函数、类型等/test就是命名空间名/命名空间中可以定义变量、函数、类型int a = 0;int val;namespace本质是定义出一个域,这个域跟全局域各自独立,不同的域可以定义同名变量C++中域有函数局部域,全局域,命名空间域,类域;域影响的是编译时语法查找
代码的注释也是本文章的重要内容,但是注释后字体颜色太浅,所以我将 // 写成 / 方便阅读
文章目录
一、C++参考文档
https://legacy.cplusplus.com/reference/
https://zh.cppreference.com/w/cpp
二、C++的第一个程序
由于C++兼容C语言的大多数语法,因此C语言代码也可在.cpp文件下运行,下面是hello world的C和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;
}
std cout等我们都看不懂,没关系,接下来我将针对这个简单的代码,将C++的一些基础知识详细讲解
三、namespace命名空间
1.namespace的价值
我们先来看一段代码
#include<stdio.h>
int a = 0;
int a = 10;
int main()
{
printf("%d", a);
return 0;
}/编译报错“a”: 重定义;多次初始化
在多人协作的大项目中,不可避免的会产生同名变量函数等,在C语言中,同一个作用域下一个变量不允许出现第二次,C++则使用namespace来避免不同代码模块之间出现命名冲突
2.namespace的定义
namespace后面跟命名空间的名字,再加上一对{},就是命名空间的格式了。{}中即为命名空间的成员,命名空间中可以定义变量、函数、类型等
namespace test
/test就是命名空间名
{
/命名空间中可以定义变量、函数、类型
int a = 0;
int add(int left, int right)
{
return left + right;
}
struct Node
{
struct Node* next;
int val;
};
}
- namespace本质是定义出一个域,这个域跟全局域各自独立,不同的域可以定义同名变量
- C++中域有函数局部域,全局域,命名空间域,类域;域影响的是编译时语法查找⼀个变量/函数/ 类型出处(声明或定义)的逻辑,所有有了域隔离,名字冲突就解决了。局部域和全局域除了会影响编译查找逻辑,还会影响变量的生命周期,命名空间域和类域不影响变量生命周期
- namespace只能定义在全局,当然它还可以嵌套定义
讲到这,我们就能用namespace解决同名冲突问题了,就是将两个同名a放到不同的命名空间中,这里我们顺便演示一下嵌套定义,test中镶嵌了one two两个命名空间
#include<stdio.h>
namespace test
{
/第一个a
namespace one
{
int a = 0;
}
/第二个a
namespace two
{
int a = 10;
}
}
int main()
{
printf("%d\n", test::one::a);
printf("%d\n", test::two::a);
}
- 如同指针解引用需要借用符号* ,命名空间借助运算符::访问命名空间中的标识符
- 项目工程中多文件中定义的同名namespace会认为是⼀个namespace,不会冲突
例如,在test.cpp文件下的fruit命名空间中加入int apple=1;在make.cpp的文件下的fruit命名空间中加入int banana=3;在最后的main函数中输出apple,banana,都是调用fruit命名空间,fruit::apple, fruit::banama
3.命名空间的使用
编译查找一个变量的声明或定义时,默认只会在局部或者全局查找,不会到命名空间里面去找,所以我们要使用命名空间的成员,有三种方法:
1. 指定命名空间访问,项目中推荐该方法
2. using将命名空间中某个成员展开,项目中经常访问的不存在冲突的成员推荐该方法
3. 展开命名空间中全部成员,项目不推荐,冲突风险很大,日常小练习程序为了方便推荐使用
#include<stdio.h>
namespace test
{
int a = 0;
int b = 10;
}
/1、指定命名空间访问
int main()
{
printf("%d\n", test::a);
return 0;
}
/2、using将命名空间某个成员展开
using test::b;
int main()
{
printf("%d\n", test::a);
printf("%d\n", b);
return 0;
}
/3、展开命名空间中全部成员
using namespace test;
int main()
{
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
C++标准库都放在⼀个叫std(standard)的命名空间中,如前面hello world程序中的cout,endl,都是std的成员,为了方便使用,所以我们将std的全部成员都展开了——using namespace std;
四、C++输入&输出
- iostream 是Input Output Stream的缩写,是标准的输入、输出流库,定义了标准的输入、输出对象,在cpp文件中要包含该头文件。(在C语言中的头文件通常有.h,注意区分)
- std::cin 是istream类的对象,标准输入流(对标C的scanf)
- std::cout是ostream类的对象,标准输出流(对标C的printf)
- std::endl是一个函数,流插入输出时,相当于插入一个换行字符加刷新缓冲区(对标C的 \n)
- <<是流插入运算符,>>是流提取运算符。(C语言还用这两个运算符做位运算左移/右移)
- cout/cin/endl等都属于C++标准库,C++标准库都放在⼀个叫std(standard)的命名空间中,所以要通过命名空间的使用方式去用他们
- ⼀般日常练习中我们可以using namespace std,实际项目开发中不建议这么展开
以下是C和CPP输入输出的对比代码,大家好好感受一下
#include<iostream>
using namespace std;
int main()
{
int a = 0;
double b = 0.4;
char c = 'x';
scanf("%d%lf", &a, &b);
printf("%d %lf\n", a, b);
return 0;
}
int main()
{
int a = 0;
double b = 0.4;
char c = 'x';
/cpp的输入输出更方便,系统会自动识别变量的类型,因此输入输出时不需要标明数据类型
/ 还支持自定义类型对象的输入输出
cin >> a;
cin >> b >> c;
cout << a << endl;
cout << b << " " << c << endl;
return 0;
}
讲到这里大家对C++应该有了一个初步的认识了,让我们重新来看C++实现的hello world
#include <iostream>
/包含头文件
using namespace std;
/将std的成员全部展开
int main()
{
cout << "hello world" << endl;
/cout输出流, <<"要输出的东西 ", endl换行和刷新缓冲区
return 0;
}
五、缺省参数(默认参数)
- 缺省参数是声明或定义函数时为函数的一个参数指定一个缺省值,如果觉得缺省这个词太抽象,可以直接把其理解为默认值。当调用函数时,如果没有为该参数传入实参,函数就会使用设定好的默认值。
- 缺省参数分为全缺省和半缺省参数。全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值(不是真的一半)。C++规定半缺省参数必须从右往左依次缺省,不能间隔跳跃给缺省参数(方便编译器理解)
- 带缺省参数的函数调用,C++规定必须从左到右依次给实参(和给缺省参数相反),不能跳跃给实参
- 函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值
#include <iostream>
using namespace std;
/ 示例1: 全缺省参数
void func1(int a = 1, int b = 2, int c = 3)
{
cout << "a=" << a << ",b=" << b << ",c=" << c << endl;
}
/ 示例2: 半缺省参数,从右向左连续缺省
void func2(int a, int b = 2, int c = 3)
{
cout << "a=" << a << ",b=" << b << ",c=" << c << endl;
}
/ 函数声明与定义分离时的缺省参数只在声明中提供
void func3(int a, int b = 2, int c = 3);
int main()
{
/ 全缺省参数调用示例
func1(); / 输出:a=1, b=2, c=3
func1(10); / 输出:a=10, b=2, c=3
func1(10, 20); / 输出:a=10, b=20, c=3
func1(10, 20, 30); / 输出:a=10, b=20, c=30
/ 半缺省参数调用示例(必须从左向右传参)
func2(100); / 输出:a=100, b=2, c=3
func2(100, 200); / 输出:a=100, b=200, c=3
func2(100, 200, 300); / 输出:a=100, b=200, c=300
/ 函数声明与定义分离的调用示例
func3(1000); / 输出:a=1000, b=2, c=3
return 0;
}
/ 函数定义(不重复缺省值)
void func3(int a, int b, int c)
{
cout << "a=" << a << ",b=" << b << ",c=" << c << endl;
}
/ 错误示例1: 半缺省参数未从右向左连续缺省
/ void errorFunc1(int a = 1, int b, int c = 3) { } / 错误: 非连续缺省
/ 错误示例2: 函数声明和定义都提供缺省值
/ void func4(int a = 1); / 声明
/ void func4(int a = 1) { } / 错误: 定义时重复提供缺省值
六、函数重载
函数重载是面向对象编程中的一个重要概念, 它允许在同一作用域内定义多个同名但参数列表不同的函数,简单来说就是C++支持在同一作用域内出现同名函数,但要求参数列表必须不同(类型、数量或顺序)
返回值类型不能单独作为重载依据,会产生歧义,编译器不知道调用哪个
#include <iostream>
using namespace std;
/1、参数类型不同
int add(int a, int b)
{
return a + b;
}
double add(double a, double b)
{
return a + b;
}
/2、参数个数不同
int add(int a, int b, int c)
{
return a + b + c;
}
/3、参数顺序不同
double add(int a, double b)
{
return a + b;
}
double add(double a, int b)
{
return a + b;
}
/ 返回值不同不能作为重载条件
/void func()
/{}
/
/int func()
/{ return 0;}
int main()
{
cout << add(1, 2) << endl;
cout << add(1.5, 2.5) << endl;
cout << add(1, 2, 3) << endl;
cout << add(1, 1.1) << endl;
cout << add(2.2, 2) << endl;
return 0;
}
七、引用
1.引用的概念和定义
- 引用就是给已经存在的变量取别名,编译器不会为引⽤变量开辟内存空间, 它和它引用的变量共用同⼀块内存空间。引用和引用对象的关系就像自己的本名和小名,都指向自己
- C++为了避免引入太多运算符,会复用C语言的一些符号,这里的引用和取地址使用了同一个符号&,大家注意区分
- 定义格式为:类型& 引用别名=引用对象;
#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;
/四个地址都相同
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
cout << &d << endl;
return 0;
}
2.引用的特性
- 引用在定义时必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再也不能引用其他实体
#include<iostream>
using namespace std;
int main()
{
int a = 10;
/int& b;是不允许的,引用必须初始化
int& b = a;
int c = 20;
/int& b = c;是不允许的,C++引用不能改变指向
return 0;
}
3.引用的使用
- 函数参数传递,通过引用传递参数可以避免对象复制,提高性能,尤其适用于大型对象,代码也很简单,使用起来很方便
/ 引用参数:直接修改原始对象
void swap(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
int main() {
int x = 1, y = 2;
swap(x, y); / x 和 y 被直接修改
cout << x << ", " << y; / 输出: 2, 1
}
- 函数返回值,返回引用可以避免返回对象时的复制开销,但需注意生命周期问题(避免返回局部变量的引用)。简单来说就是函数返回引用时可以直接修改返回值,但是得满足返回的是可修改的左值,以及引用对象在函数外部有效
int& getMax(int& a, int& b)
{
return (a > b) ? a : b;
}
int main()
{
int x = 5, y = 10;
getMax(x, y) = 20; / 修改最大值为 20
cout << y; / 输出: 20
}
- 避免返回局部变量的引用:局部变量在函数结束后销毁,引用会变成悬空引用。
int& func()
{
int x = 10;
return x; / 错误:返回局部变量的引用
}
引用和指针在实践中相辅相成,功能有重叠性,但是各有特点,互相不可替代。C++的引用跟其他语言的引用(如Java)是有很大的区别的,除了用法,最大的点是C++引用定义后不能改变指向, Java的引用可以改变指向。
4. const引用
可以引用一个const对象,但是必须用const引用。const引用也可以引用普通对象,因为对象的访问权限在引用过程中可以缩小,但是不能放大
#include<iostream>
using namespace std;
int main()
{
const int a = 10;
/int& ra = a;是不允许的,const修饰a后,a为常量。
/ 而这里的ra是可修改的变量,是对a的访问权限地放大
const int& ra = a;
/ra++;是不允许的,因为此时ra是常量
int b = 20;
const int& rb = b;
/这里对rb进行const修饰是允许的
/这里的引用是对b访问权限的缩小
int c = 1;
/int& rc = c * 3;是不允许的,c*3产生了一个临时的整数值
/ C++中默认临时值为不可改变的常数,引用常数是对使用权限的放大
/引用必须绑定到一个左值(可寻址的变量),而不能直接绑定到临时对象(右值)
const int& rc = c * 3;
double d = 1.1;
/int& rd = d;是不允许的,double强制转换成int型
/得先额外拷贝整数部分,拷贝值为常量
/引用常数是对使用权限的放大
const int& rd = d;
return 0;
}
5.指针和引用的关系
- C++中指针和引用就像两个性格迥异的亲兄弟,指针是哥哥,引用是弟弟,在实践中他们相辅相成,功能有重叠性,但是各有自己的特点,互相不可替代
- 语法概念上引用是一个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间
- 引用在定义时必须初始化,指针建议初始化,但是语法上不是必须的
- 引用在初始化时引用一个对象后,就不能再引用其他对象,而指针可以在不断地改变指向对象
- 引用可以直接访问指向对象,指针需要解引用才是访问指向对象
- sizeof中含义不同,引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下 占4个字节,64位下是8byte)
- 指针很容易出现空指针和野指针的问题,引用很少出现,使用起来相对更安全⼀些
八、Inline
1.内联函数的使用
用inline修饰的函数叫做内联函数,在 C++ 中,inline 关键字用于向编译器建议将函数体直接替换函数调用,从而减少函数调用的开销
#include<iostream>
using namespace std;
/声明内联函数
inline int add(int a, int b)
{
return a + b;
}
int main()
{
int result = add(3, 4);
/可能被编译器展开为:int result=3+4;
return 0;
}
- 使用inline可以避免压栈、跳转和返回操作,适用于简短函数。但inline对编译器而言只是一个建议,也就是说,你加了inline编译器也可以选择不展开(如循环递归等复杂逻辑)
- vs编译器debug版本下面默认是不展开inline的,这样方便调试
- inline不建议声明和定义分离到两个文件,分离会导致链接错误。因为编译器一旦将一个函数作为内联函数处理,就会在调用位置展开,即该函数是没有地址的,也不能在其他源文件中调用,故一般都是直接在源文件中定义内联函数的
2. 为什么需要内联函数
普通函数调用的开销太大
int square(int x)
{
return x * x;
}
int a = square(5);
/ 调用过程:
/ 1. 将参数压栈
/ 2. 跳转到函数地址
/ 3. 执行函数体
/ 4. 返回结果
/ 5. 恢复调用现场
内联展开的优化(效率得到了提高)
inline int square(int x)
{
return x * x;
}
int a = square(5);
/ 直接展开为:int a = 5 * 5;
3.宏函数&内联函数
宏函数通过#define实现,易出错,调试困难,且不进行类型检查
#define SQUARE(x) ((x) * (x))
内联函数
inline int square(int x)
{
return x * x;
}
九、nullptr
在 C++ 中,nullptr 是一个关键字,用于表示空指针常量。它是 C++11 引入的特性,用于替代传统的 NULL 宏,解决了 NULL 在函数重载和模板推导中的二义性问题。
在C++中使用nullptr来完成C语言中null的工作
1.NULL的缺陷
在C++中,NULL通常被定义为整数0或(void*)0,这可能会导致函数重载歧义
void func(int x); /版本1
void func(char* ptr); /版本2
func(NULL);
/调用哪个版本?可能导致编译错误或意外调用
/NULL 被视为 int,可能错误调用 func(int)
2.nullptr的特性
nullptr是⼀种特殊类型的字面量,它可以转换成任意其他类型的指针类型。使用nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,而不能被转换为整数类型
/nullptr使用场景
int* ptr1 = nullptr; /合法
char* ptr2 = nullptr; /合法
int num = nullptr; /非法:不能转换为整数
/解决函数重载歧义
void func(int x); / 版本1
void func(char* ptr); / 版本2
func(nullptr); /明确调用 func(char* ptr)
更多推荐



所有评论(0)