C++基础设计
一、基础程序设计结构
以.cpp作为文件后缀名的源代码文件
#include<iostream>//用于输入输出 using namespace std;//可以省略cout,endl等的前缀 int main(){ cout<<"test.cpp"<<endl; return 0; }输入:cin
cin >> 变量;输出:cout
cout << "内容" << 变量 << endl;二、引用
引用是 C++ 独有的语法,C 语言没有;引用不是新变量,只是已有变量的别名
语法
数据类型 &引用名 = 原变量; //示例 int a = 10; int &b = a; // b 是a的引用(别名&这里不是取地址符,是引用声明标记a和b代表同一块内存空间,不是复制一份新数据int a = 10; int &b = a; b = 20; cout << a; // 输出 20!修改b,a跟着变引用 b 就是 a 的外号,叫 b 和叫 a,都是同一个人。
使用特点
引用必须初始化;
#include <iostream> using namespace std; int main() { int a = 10; int &r1 = a; // 定义同时初始化 int &r2; // 编译报错,引用没有初始化 return 0; }引用的数据类型必须和被引用的变量的数据类型一致;
int main() { int a = 10; double b = 3.14; int &r1 = a; // 类型匹配 int &r2 = b; // double变量,不能用int引用绑定 return 0; }不存在多级引用(没有
&&引用的引用);int main() { int a = 10; int &r = a; int &&rr = r; // 不是多级引用!这里&&是右值引用(C++11新语法,和本基础章节无关) int & &rr = r; // 编译报错,不存在引用的引用(多级引用) return 0; }不存在引用类型的数组(数组里面存放引用);
int main() { int a=1,b=2,c=3; int arr1[5] = {a,b,c}; int &arr2[5] = {a,b,c}; // 非法:引用数组,不能创建 int (&ref_arr)[5] = arr1; // ref_arr 是整个arr1数组的引用 return 0; }普通引用不能引用常量(字面量 / 临时值)
int main() { int &r = 100; // 普通引用,不能绑定字面常量 const int &cr = 100; // const常引用,可以绑定常量、临时值 return 0; }
函数参数
值传递
实参拷贝一份副本给形参,形参是独立的新变量,和外面实参互不干扰。 函数内部修改的只是副本,不会影响外部原始实参。
#include <iostream> using namespace std; void swap(int x, int y) // x,y 是实参的拷贝副本(值传递) { int temp = x; x = y; y = temp; cout << "函数内部 x=" << x << " y=" << y << endl; } int main() { int a = 10, b = 20; swap(a, b); cout << "函数外部 a=" << a << " b=" << b << endl; return 0; } /* 函数内部 x=20 y=10 函数外部 a=10 b=20 */特点:副本独立,修改形参,外面不变
缺点:拷贝数据,数据量大的时候效率低;无法修改外部变量
地址传递
把实参的地址拷贝给指针形参。 形参指针保存实参内存地址,通过解引用
*,可以直接操作外部原始变量。#include <iostream> using namespace std; void swap(int *x, int *y) // x,y是指针变量,接收实参地址 { int temp = *x; *x = *y; *y = temp; } int main() { int a = 10, b = 20; swap(&a, &b); // 调用时要取地址 &a &b cout << "a=" << a << " b=" << b << endl; return 0; } //a = 20,b = 10优点:不拷贝原始数据,可以修改外部变量
缺点:
- 调用时需要手动写
&取地址 - 函数内部取值需要写
*解引用,写法麻烦 - 指针存在风险:有可能传入空指针
nullptr,程序崩溃
- 调用时需要手动写
引用传递
形参是实参变量的别名,形参和实参同一块内存,没有拷贝副本。 在函数内部操作形参,等价于直接操作外面原始实参。
#include <iostream> using namespace std; void swap(int &x, int &y) // &表示引用;x是main中实参a的别名,y是b别名 { int temp = x; x = y; y = temp; } int main() { int a = 10, b = 20; swap(a, b); // 调用直接写变量名!不用写&取地址 cout << "a=" << a << " b=" << b << endl; return 0; } //a = 20;b = 10优点
不需要拷贝副本,节省内存,效率高(传递结构体、大数组优势巨大)
可以修改外部实参的值(和指针效果一样)
写法简单:调用时直接传变量,不用
&,函数内不用*解引用不存在空引用,比指针更安全
底层:引用传递编译器底层还是用指针实现,但是语法层面简化了。
引用 vs 指针
特点 引用 & 指针 * 本质 变量别名,语法糖,没有独立内存 独立变量,存地址,占用内存 初始化 定义时必须绑定变量 可以先定义,后面赋值,可空 能否改绑 一旦绑定,不能换目标 可以随时修改指向别的变量 使用方式 直接写名字,不用解引用 *取值需要解引用 *p地址 &引用和原变量地址完全相同指针变量本身有自己独立地址 const 常引用
const关键字修饰引用,常引用可以引用常量,也可以引用变量;如果只读取、不修改实参**,推荐使用
const 类型 &**常引用独有的特性
常引用引用变量
int var = 100; const int &ref1 = var; cout << "var = " << var << ", ref1 = " << ref1 << endl; var = 200; cout << "var = " << var << ", ref1 = " << ref1 << endl; // ref1 = 300; // 错误,不能通过常引用修改变量的值常引用引用常量(普通引用不能引用常量)
// int& ref2 = 10; // 错误,普通引用不能引用常量 const int &ref2 = 10; // 正确,常引用可以引用常量 cout << "ref2 = " << ref2 << endl;常引用可以引用不同类型的变量(会进行隐式类型转换,生成临时变量)
double d = 3.14; // int& ref3 = d; // 错误,类型不匹配 const int &ref3 = d; // 正确,常引用可以引用不同类型(生成临时变量) cout << "d = " << d << ", ref3 = " << ref3 << endl; d = 6.28; cout << "d = " << d << ", ref3 = " << ref3 << endl; // ref3的值没有改变,因为它引用的是临时变量常引用作为函数参数(保护实参不被修改,同时支持传递常量)
void printNum(const int &num) { cout << "printNum: " << num << endl; // num = 456; // 错误,不能修改常引用参数 } int var = 100; void printNum(const int &num); printNum(123); // 可以传递常量 printNum(var); // 可以传递变量布尔类型
常见坑点
常引用不能修改引用本身指向的值,但原变量可改
int a = 10; const int &r = a; a = 20; cout << r; //输出20 // r = 30; // 报错,不能通过r修改const 常引用绑定其他类型,会产生临时变量,断开联动。一旦发生类型转换,引用绑定的是临时值,不是原变量。
区分「const 变量」和「const 引用」
const int a = 10; const int &r = a; // 合法,常引用绑定const变量 int &r2 = a; // 普通引用不能绑定const变量!a 是只读常量,如果允许普通引用
int&绑定,就可以通过引用修改常量,语法禁止。
三、作用域
函数原型作用域
变量的作用域范围在函数原型内,即函数声明中变量所在的参数列表内。
参数名仅在函数声明的参数列表内有效,函数体外部无法访问这些参数名int isEqual(int a, int b); // a, b 的作用域仅限于此声明 // 这里不能访问 a 或 b,因为它们属于函数原型作用域 int c = a; c = b;函数定义时的参数名可以与声明时不同bool isEqual(int x, int y) { // 定义时可以使用不同的参数名 return x == y; }
局部作用域
变量的作用域范围在函数体内,或者在复合语句(花括号
{})内内层作用域可以访问外层作用域的变量(由内向外查找)void func() { int localVar = 10; // 外层局部变量 cout << "外层 localVar = " << localVar << endl; { cout << "内层 localVar = " << localVar << endl; //10 } }外层作用域无法访问内层作用域的变量void func() { { int innerVar = 20; // 复合语句内的局部变量 cout << "内层 innerVar = " << innerVar << endl; } // cout << innerVar; // 错误!外层无法访问内层变量 }同名变量遵循"就近原则":内层变量会隐藏外层同名变量void func() { int localVar = 10; // 外层局部变量 cout << "外层 localVar = " << localVar << endl; { int localVar = 30; // 内层同名变量,隐藏外层变量 cout << "内层 localVar = " << localVar << endl; // 输出30,不是10 } cout << "外层 localVar = " << localVar << endl;// 输出10 }
全局作用域
全局变量从声明处开始到整个程序结束都能访问。
所有函数都可以访问全局变量,生命周期贯穿整个程序运行期间(存储在静态存储区)int globalVar = 100; // 全局变量,整个程序都能访问 void func1() { cout << "func1: globalVar = " << globalVar << endl; } void func2() { cout << "func2: globalVar = " << globalVar << endl; } int main() { cout << "main: globalVar = " << globalVar << endl; func1(); func2(); cout << "main: globalVar = " << globalVar << endl; return 0; }全局变量默认初始化为0int globalVar; // 全局变量,整个程序都能访问 void func1() { cout << "func1: globalVar = " << globalVar << endl;//0 } void func2() { globalVar = 200; // 修改全局变量 cout << "func2: globalVar = " << globalVar << endl;//200 } int main() { func1(); func2(); return 0; }
类作用域
类内部的成员能够访问的区域,被称为类作用域
类成员(成员变量和成员函数)的作用域在类内部,类外部访问需要通过对象、指针或引用,并使用 . 或 -> 操作符class MyClass { private: int privateVar = 100; // 私有成员,类外不可访问 public: int publicVar = 200; // 公共成员,类内外都可访问 void classFunc() { cout << "privateVar = " << privateVar << endl; cout << "publicVar = " << publicVar << endl; } }; int main() { MyClass obj; obj.classFunc(); // 通过对象调用成员函数 // cout << obj.privateVar; // 错误!私有成员类外不可访问 cout << "obj.publicVar = " << obj.publicVar << endl; return 0; }类成员函数内部可以直接访问类的所有成员(包括private成员),类的静态成员属于类作用域,不属于对象class MyClass { private: int privateVar = 100; // 私有成员,类外不可访问 public: int publicVar = 200; // 公共成员,类内外都可访问 void classFunc() { // 类内部可以直接访问所有成员 cout << "privateVar = " << privateVar << endl; cout << "publicVar = " << publicVar << endl; } };
命名空间作用域
在指定的命名空间内,进行有效访问的区域。
**语法** namespace 命名空间名 { // 变量、函数、类等 }命名空间用于组织代码,避免命名冲突namespace MyNamespace { int namespaceVar = 200; void namespaceFunc() { cout << "命名空间中的函数:namespaceVar = " << namespaceVar << endl; } class MyClass { public: void classFunc() { cout << "class"<< endl; } } } int main() { return 0; }命名空间内的成员需要使用 :: 作用域区分符访问namespace MyNamespace { int namespaceVar = 200; void namespaceFunc() { cout << "命名空间中的函数:namespaceVar = " << namespaceVar << endl; } } int main() { // 使用作用域区分符访问 cout << "MyNamespace::namespaceVar = " << MyNamespace::namespaceVar << endl; MyNamespace::namespaceFunc(); return 0; }可以使用 using namespace 指令简化访问namespace MyNamespace { int namespaceVar = 200; void namespaceFunc() { cout << "命名空间中的函数:namespaceVar = " << namespaceVar << endl; } } int main() { // 使用using指令简化 using namespace MyNamespace; cout << "使用using后:namespaceVar = " << namespaceVar << endl; return 0; }命名空间可以嵌套namespace MyNamespace { // 嵌套命名空间 namespace InnerSpace { int innerVar = 300; } } int main() { // 访问嵌套命名空间 cout << "MyNamespace::InnerSpace::innerVar = " << MyNamespace::InnerSpace::innerVar << endl; return 0; }命名空间可以跨文件
四、函数的默认参数
概念C++程序允许用户给函数预先设置参数的默认值。如果用户不为该函数传递实际参数,则函数使用参数的默认值;如果用户传递了实际参数,则函数使用实际参数。(默认参数是在编译阶段确定的,编译器会在调用点将缺失的参数替换为默认值。)
语法// 函数声明时设置默认参数 返回值类型 函数名(数据类型 变量名1=值, 数据类型 变量名2=值...); //三个参数(String类型,int类型,String类型) void printInfo(string name = "张三", int age = 18, string gender = "男");使用规则声明与定义的关系(如果函数声明处添加了参数默认值,则函数定义处可以不加默认值)
// 声明时设置默认值 void printInfo(string name = "张三", int age = 18, string gender = "男"); // 定义时可以不加默认值 void printInfo(string name, int age, string gender) { cout << "姓名: " << name << endl; cout << "年龄: " << age << endl; cout << "性别: " << gender << endl; } //**注意**:如果函数只有定义没有声明,则默认参数必须在定义处设置: void printInfo(string name = "张三", int age = 18, string gender = "男") { // 函数体 }默认参数的顺序(在拥有多个参数的函数使用默认值时,默认值的右边不能有非默认值存在)
// 正确:默认值都在右边 void func1(int a, int b = 10, int c = 20); // 错误:默认值右边有非默认值 // void func2(int a = 10, int b, int c); // 错误:默认值之间有非默认值 // void func3(int a, int b = 10, int c, int d = 30);引用作为函数参数的默认值(普通引用不能绑定到临时对象,因此普通引用参数不能加默认值)
//普通引用 `int &ref` 必须绑定到一个左值(可修改的变量); //`10` 是一个右值(临时对象),无法绑定到普通引用; //`const int &ref` 可以绑定到右值,因为它承诺不修改引用的对象 // void func1(int &ref = 10);// 错误:普通引用不能绑定到常量10(临时对象) // 正确:const引用可以绑定常量 void func2(const int &ref = 10); // 正确:使用指针代替引用 void func3(int *p = nullptr); // 正确:普通参数可以加默认值 void func4(int num = 10);
五.内联函数
概念当一个函数在程序中被频繁大量调用时,可以将这个函数声明为内联函数(Inline Function)。
**普通函数调用的开销**: 1. 主调函数暂停执行 2. CPU切换到被调函数,开辟函数栈帧(内存) 3. 保存当前执行状态(现场) 4. 重置程序计数器(PC),跳转到被调函数 5. 执行被调函数 6. 被调函数结束后,释放函数栈帧 7. 恢复现场(恢复寄存器、程序计数器等) 8. 继续执行主调函数 **内联函数的优化**: 内联函数在编译之后会将函数体**直接嵌入**到函数调用处,消除函数调用的开销,提高执行效率。 **形象比喻**: - **普通函数**:就像打电话,需要拨号、等待接通、通话、挂断,有很大的开销 - **内联函数**:就像面对面交谈,直接传递信息,几乎没有开销语法inline 返回值类型 函数名(形参列表); inline int add(int a, int b) { //函数体 }语法原则
函数的语句小于等于5行:函数体过于庞大时,内联展开会导致代码体积急剧增加
// 不适合内联:函数体过大 inline int computeSum(int arr[], int size) { int sum = 0; for (int i = 0; i < size; i++) { // 有循环 sum += arr[i]; } return sum; }函数内没有循环结构:循环会导致代码重复展开,增加代码体积
// 不适合内联:函数体过大 inline int computeSum(int arr[], int size) { int sum = 0; for (int i = 0; i < size; i++) { // 有循环 sum += arr[i]; } return sum; }函数不能递归:递归函数无法在编译时确定展开次数
// 不适合内联:递归函数 inline int factorial(int n) { if (n == 0) return 1; return n * factorial(n - 1); // 递归 }
工作原理//普通函数调用: int add(int a, int b) { return a + b; } int main() { int x = add(3, 5); // 编译后:call add return 0; } //内联函数展开 inline int add(int a, int b) { return a + b; } int main() { int x = add(3, 5); // 编译后:int x = 3 + 5; return 0; } //编译器会将 `add(3, 5)` 直接替换为函数体 `3 + 5`,从而避免了函数调用的开销。