欢迎光临
我们一直在努力

C++基础知识

一. 标准输入输出 (Standard I/O)

输入输出
C++ 使用 iostream 库进行输入输出,要包含头文件,其包含两个基础类型istream与ostream,分别代表输入流与输出流,与 C 语言的 printf 和 scanf 不同,C++ 使用的是流 (Stream) 的概念。

cout:标准输出流(通常是屏幕),名为istream类型对象

cin:标准输入流(通常是键盘),名为ostream类型对象

<<:流插入运算符(用于输出)。

>>:流提取运算符(用于输入)

例如:

#include <iostream>

int main()
{
    int age;
    std::cout << "请输入您的年龄: "; 
    std::cin >> age; // 从键盘读取数据赋值给 age
    std::cout << "您今年 " << age << " 岁。" << std::endl; 
    return 0;
}

前缀std::指出cin,cout,endl是定义在名为std的命名空间中的,而::为作用域运算符,来指出我们想使用定义在std中的名字。

易错点
方向写反:新手常把 cin >> 写成 cin <<。记住:箭头指向数据流动的方向(cin 的数据流向变量,变量的数据流向 cout)。

缺少类型安全性:虽然 cin 会自动识别类型,但如果用户输入了非法字符(如要求输入整数却输入了字母),cin 会进入错误状态,后续输入将失效。

二. 命名空间 (namespace)

1.规则
为了防止在大规模项目中出现变量名或函数名冲突,C++ 引入了 namespace。它就像是一个“姓氏”。

基础用法
所有的标准库函数(如 cout)都位于 std 命名空间中。


2.使用

<1>.不展开,比较安全

#include <iostream>

namespace my_space
{
int a = 10;
void print()
{
std::cout << "From my_space" << std::endl;
}
}

int main()
{
// 显式指定命名空间访问
std::cout << my_space::a << std::endl;
my_space::print();
return 0;
}

<2>.展开部分

#include <iostream>

namespace my_space
{
int a = 10;
int b = 20;
void print()
{
std::cout << "From my_space" << std::endl;
}
}

// 仅展开需要的成员
using my_space::a;
using std::cout;

int main()
{
// 直接使用a和cout
cout << a << std::endl;
// 未展开的成员需要显式访问
cout << my_space::b << std::endl;
my_space::print();
return 0;
}

<3>.全部展开

#include <iostream>
//全部展开std
using namespace std;

namespace my_space
{
int a = 10;
void print()
{
cout << "From my_space" << endl;
}
}

// 全部展开 my_space
using namespace my_space;

int main() {
// 直接使用,无需 my_space::
cout << a << endl;
print();
return 0;
}

易错点
命名污染:过度使用 using namespace std;。在小型练习中没问题,但在大型项目中,如果你的变量名恰好和标准库里的某个函数同名,会导致难以察觉的编译错误。

三.缺省参数

缺省参数是声明或定义函数时为函数的参数指定一个缺省值。

规则:

1.在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参,缺省参数分为全缺省和半缺省参数。(有些地方把缺省参数也叫默认参数)

例如:

#include <iostream>
using namespace std;

// 全缺省:三个参数都给了默认值
void func(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << ", b = " << b << ", c = " << c << endl;
}

int main()
{
func(); // 不传参:a=10, b=20, c=30
func(1); // 传1个:a=1, b=20, c=30
func(1, 2); // 传2个:a=1, b=2, c=30
func(1, 2, 3); // 传3个:a=1, b=2, c=3
return 0;
}

2.全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值。

例如:

#include <iostream>
using namespace std;

// 半缺省:b和c给默认值,a不给 → 合法(从右往左连续)
void func(int a, int b = 20, int c = 30)
{
cout << "a = " << a << ", b = " << b << ", c = " << c << endl;
}

// ❌ 错误示例:a给默认值,b不给,c给 → 不合法(不是从右往左连续)
// void func(int a = 10, int b, int c = 30) { … }

int main()
{
func(1); // 传1个:a=1, b=20, c=30
func(1, 2); // 传2个:a=1, b=2, c=30
func(1, 2, 3); // 传3个:a=1, b=2, c=3
// func(); // ❌ 错误:a没有默认值,必须传参
return 0;
}

3.带缺省参数的函数调用,C++规定必须从左到右依次给实参,不能跳跃给实参。

例如:

#include <iostream>
using namespace std;

void func(int a, int b = 20, int c = 30)
{
cout << "a = " << a << ", b = " << b << ", c = " << c << endl;
}

int main()
{
func(1); // ✅ 合法:传a,b和c用默认
func(1, 2); // ✅ 合法:传a、b,c用默认
// func(1, , 3); // ❌ 错误:不能跳过b直接传c
return 0;
}

4.函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值。

例如:

#include <iostream>
using namespace std;

// 声明:缺省参数写在这里
void func(int a = 10, int b = 20);

// 定义:不能再写缺省参数
void func(int a, int b)
{
cout << "a = " << a << ", b = " << b << endl;
}

int main()
{
func(); // ✅ 合法:a=10, b=20
func(5); // ✅ 合法:a=5, b=20
return 0;
}

四. 引用 (Reference)

1.使用
在 C++ 的引用语法里, &  是引用声明符,它的作用是告诉编译器:当前定义的变量是一个“引用”(即另一个变量的别名),与原变量共处一块空间。

语法:类型&引用名=原变量;

例如:

#include <iostream>
using namespace std;
int main()
{
int a=10;
int&ra=a;
int&rb=ra;
int&rc=rb;
cout<<a<<' '<<ra<<' '<<rb<<' '<<rc<<endl;
return 0;
}

上面的ra,rb,rc均为a的别名。

作用:可以用于引用传参和引用作返回值,来减少拷贝,提高效率。

2.特点

<1>.定义时必须初始化

int a = 10;
int& ref = a; //正确:定义时初始化
// int& ref; //错误:未初始化,编译报错

<2>.一个变量可以有多个引用

int a = 10;
int& ref1 = a;
int& ref2 = a; //正确:a有两个引用ref1、ref2

ref1 = 20;
cout << a << " " << ref2 << endl; // 输出 20 20

<3>.引用一旦引用一个实体,在不能引用其它的实体

int a = 10;
int b = 20;
int& ref = a; // 绑定a
ref = b; // 不是绑定b,而是把b的值赋给ref(即 a = 20)
cout << a << endl; // 输出 20
cout << &ref << " " << &a << endl; // 地址相同,仍绑定a

3.const引用

特点:
<1>.可以引用一个const对象,但是必须用const引用。const引用也可以引用普通对象,因为对象的访问权限在引用过程中可以缩小,但是不能放大。

#include <iostream>
using namespace std;

int main()
{
int a = 10;
// int& rb = a * 3; //编译报错:无法绑定临时对象到非const引用
const int& rb = a * 3;//合法:const引用接受带常性的临时对象
cout << rb << endl; // 输出30,临时对象生命周期随const引用延长
return 0;
}

<2>.不过需要注意的是类似int& rb=a*3;double d=12.34;int& rd=d;这样一些场景下a*3的和结果保存在一个临时对象中,int&rd=d也是类似,在类型转换中会产生临时对象存储中间值,也就是时,rb和rd引用的都是临时对象,而C++规定临时对象具有常性,所以这里就触发了权限放大,必须要用常引用才可以。

#include <iostream>
using namespace std;

int main()
{
double d = 12.34;
// int& rd = d;//编译报错:double转int生成临时对象,非const引用不能绑
const int& rd = d;//合法:const引用绑定带常性的临时对象
cout << rd << endl;// 输出12,取临时对象的值(类型转换后结果)
return 0;
}

<3>.所谓临时对象就是编译器需要一个空间暂存表达式的求值结果时临时创建的一个未命名的对象, C++中把这个未命名对象叫做临时对象。

注意:

#include <iostream>
using namespace std;

int main()
{
int a = 20; // 普通对象:可读可写
const int& ca = a;//合法:权限从可写→只读,缩小允许
cout << ca<< endl; //输出20
a = 30; // 原对象仍可改,ca会同步变(共享内存)
cout << ca << endl;//输出30 ✅
return 0;
}

总结:所有生成临时对象的场景(表达式计算、类型转换),临时对象都有常性,只能用const引用绑定;const引用绑普通对象,本质是主动缩小访问权限,完全合法。

五.函数重载与nullptr关键字

1.函数重载

特点:在C++中, 允许你使用同一个函数名来定义多个功能相似但参数不同的函数,在C语言中,如果想写一个加法函数,可能会遇到这样的情况,处理整数:add_int(int a, int b),处理浮点数:add_float(float a, float b),处理三个整数:add_three_ints(int a, int b, int c),但在C++中一个方程式就可以表示这三个。

核心定义:指在同一个作用域内,可以有一组函数名相同,但参数列表不同的函数。

要构成重载,函数的参数列表必须满足以下至少一个条件不同:

<1>.参数类型不同: void func(int a) 和 void func(double a)。

例如:

#include <iostream>
using namespace std;
// 版本 1:处理两个整数
int add(int a, int b)
{
return a + b;
}
// 版本 2:处理两个浮点数
double add(double a, double b)
{
return a + b;
}
int main()
{
// 编译器会根据你传的参数类型,自动“点菜”
cout << "1 + 2 = " << add(1, 2) << endl; // 匹配版本 1
cout << "1.5 + 2.3 = " << add(1.5, 2.3) << endl; // 匹配版本 2
return 0;
}

<2>.参数个数不同: void func(int a) 和 void func(int a, int b)。

例如:

#include<iostream>
using namespace std;
int fun1(int x,int y)
{
return x+y;
}
int fun1(int x,int y,int z)
{
return x+y+z;
}
int main()
{
int ret1=fun1(4,6);
int ret2=fun1(4,5,6);
cout<<"ret1="<<ret1<<endl;
cout<<"ret2="<<ret2<<endl;
return 0;
}

<3>.参数顺序不同: void func(int a, double b) 和 void func(double a, int b)。

#include <iostream>
using namespace std;
// 版本 A:顺序是 (字符, 整数)
void show(char level, int id)
{
cout << "等级是: " << level << ", 编号是: " << id <<endl;
}
// 版本 B:顺序是 (整数, 字符) —— 顺序换了,构成了重载
void show(int id, char level)
{
cout << "编号是: " << id << ", 等级是: " << level << endl;
}
int main()
{
// 编译器根据你传参数的顺序来自动选择
show('A', 101); // 匹配 (char, int) -> 调用版本 A
show(102, 'B'); // 匹配 (int, char) -> 调用版本 B
return 0;
}

易错点:只有返回值类型不同(经典错误)

例如:

#include <iostream>
// 函数 1:返回整数
int getData(int x)
{
return x;
}
// 函数 2:返回小数
// ❌ 报错!编译器会提示“无法重载仅按返回类型区分的函数”
double getData(int x)
{
return (double)x;
}
int main()
{
//这样调用,编译器该选哪一个呢?
getData(10);
return 0;
}

2.nullptr关键字

在C语言中NULL 的完整定义形式为:

#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

 在 C 语言中:void* 可以自动转换为任何类型的指针,所以用 ((void *)0) 非常安全且确, C++ 语言中:C++ 具有更严格的类型检查,不允许 void* 隐式转换成其他指针(比如 int*)。如果 C++ 也用 ((void *)0),那么 int* p = NULL; 就会报错。因此,C++ 只能折中把 NULL 定义为整数 0。

例如在C++中有这样的代码:

#include<iostream>
using namespace std;
void fun(int x)
{
cout<<"fun1(x)"<<endl;
}
void fun(int*ptr)
{
cout<<"fun2(ptr)"<<endl;
}
int main()
{
fun(0);//调用第一个函数
fun(NULL); //在C++中NULL会被确认为0,所以编译器可能会报错,或者调用第一个函数
return 0;
}

nullptr关键字是一个指针字面量,属于一种特殊的类型 std::nullptr_t,它不是整数,但可以安全地转换为任何类型的指针,而不是整数类型。

此时上面的代码可以改为:

#include<iostream>
using namespace std;
void fun(int x)
{
cout<<"fun1(x)"<<endl;
}
void fun(int*ptr)
{
cout<<"fun2(ptr)"<<endl;
}
int main()
{
fun(0);//调用第一个函数
fun(nullptr);//调用第二个函数
return 0;
}

六.inline函数与宏

在C语言中,宏(#define)本质上是简单的文本替换,但是稍不注意,运算就会发生错误

例如:

#include<stdio.h>
#define SQUARE(x) x * x
int main()
{
// 期望的结果是 (1+2) * (1+2) = 9
// 实际替换结果:1 + 2 * 1 + 2 = 5
int result = SQUARE(1 + 2);
return 0;
}

修正方案: 永远给参数和结果加括号。 #define SQUARE(x) ((x) * (x))

但是就算给参数和结果加括号,也可能会出错,例如:

#include<stdio.h>
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main()
{
int i = 5, j = 3;
// 你期望 i 变成 6,结果返回 6
// 实际替换结果:((++i) > (j) ? (++i) : (j))
int res = MAX(++i, j);
// 此时 i 竟然变成了 7!因为它在比较时加了一次,在返回结果时又加了一次。
return 0;
}

针对以上的错误,inline函数就可以解决。

内联函数语法形式:

inline 返回类型 函数名(参数列表)
{
// 函数体

return 返回值;
}

特点:

<1>.编译器在编译阶段将调用处的函数名直接替换为完整的函数体代码。

<2>.由于没有了函数调用的参数传递、栈帧建立和恢复、跳转等开销,程序运行速度更快。

<3>.内联函数是函数,编译器会对参数类型和返回值进行严格检查,避免了宏替换中常见的逻辑漏洞。

<4>.inline 只是给编译器提个建议。编译器会根据函数的大小、复杂度以及自身的优化策略来决定是否真正内联,函数体包含复杂的 while、for 循环、switch 语句或递归调用,编译器通常会拒绝内联。

<5>.inline不建议定义与声明分离到两个文件,分离会导致错误,通常定义在 .h 文件中。

例子:

#include <iostream>
using namespace std;
// 将宏改为内联函数
inline int square(int x)
{
return x * x;
}
int main()
{
int a = 5;
// 此时调用 square(++a)
int result = square(++a); //相当与(++a)*(++a),即6*6
std::cout << "result = " << result << std::endl; // 输出 36
std::cout << "a = " << a << std::endl; // 输出 6

return 0;
}

缺点:

<1>.如果一个内联函数比较大,且在程序中被调用了成百上千次,最终编译出来的可执行文件体积会显著增大。

<2>.如果函数太复杂(有循环、递归、或是代码量太多),编译器会为了保护程序性能而自动取消内联。

所以inline函数应用于函数调用次数较少,函数内部不复杂,函数体较小的情况

下一篇为类与对象(上)

赞(0)
未经允许不得转载:171主机测评 » C++基础知识
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址