【C++】入门阶段
一、初始化
C++中的初始化指为变量赋予初始值的过程。初始化方式多样,适用于不同场景。
char cha=0;
char chb={'0'};
char chc=('\0);
char chd=cha;
char che{};
注意事项
- 优先使用列表初始化(
{}
),避免窄化转换风险。 - 在c++11中{ }在变量,指针,数组,结构体上达到统一。
- { }对数据进行严格的数据类型检查,而在c语言中检查不严格。
- 类成员初始化推荐使用成员初始化列表,效率高于构造函数内赋值。
- 动态内存需手动管理,避免内存泄漏。
二、输入输出
c语言的输入输出
C语言中标准输入输出主要通过stdio.h
头文件提供的函数实现:
格式化输入输出
printf()
:格式化输出到标准输出(屏幕)scanf()
:从标准输入(键盘)读取格式化输入
#include <stdio.h>
int main() {int num;printf("Enter an integer: ");scanf("%d", &num);printf("You entered: %d", num);return 0;
}
注意事项
- 使用
scanf()
时注意变量前的&
符号(数组名除外) - 输出浮点数时可使用
%.2f
等格式控制小数位数 - 输入输出对数据类型太依赖,初始化的数据类型,输出也是该类型
c++的输入输出
C++ 的输入输出主要通过标准库 <iostream>
提供,包含 cin
(标准输入)和 cout
(标准输出)等对象。
头文件引入
#include <iostream>
using namespace std; // 避免每次写 std::
标准输出(cout):cout
用于向控制台输出数据,使用 <<
运算符连接内容。
int num = 42;
cout << "Hello, World!" << endl; // endl 换行
cout << "Number: " << num << "\n"; // "\n" 也可换行
格式化输出:通过 <iomanip>
实现格式控制(如小数位数、对齐):
#include <iomanip>
double pi = 3.1415926;
cout << fixed << setprecision(2) << pi; // 输出 3.14
标准输入(cin):cin
用于从控制台读取数据,使用 >>
运算符分隔输入。
int age;
string name;
cout << "Enter name and age: ";
cin >> name >> age; // 输入 "Alice 25"
处理字符串输入:cin
以空格为分隔符,读取整行需用 getline
:
string fullName;
cin.ignore(); // 清除缓冲区残留的换行符
getline(cin, fullName); // 读取整行
总结
- 输入输出只针对于基本的数据类型,不包含自定义的类型
- endl相当于'\n'
三、const关键字
const关键字在C和C++中的区别
总结
const在修饰的整型类型常量时,在读取其值的时候,都会在编译的时候将其常量值替换掉;相当于c语言的 #define IP 3.14
而在c语言中,int b=a;从a这个存储单元取数据放入寄存器中,再将寄存器的值给b这个变量,因此每次都从内存中取而不是替换。作用于编译的时候做出检查。a是常量不允许修改,但在运行时a并不是常量值,而是从内存空间中取,这导致在c语言中不能用a定义数组,a仍然是一个变量。
作用域差异
在C语言中,const
修饰的变量默认是外部链接(external linkage),除非显式使用static
限定。在C++中,const
修饰的变量默认是内部链接(internal linkage),类似于static
的效果。若需在C++中实现外部链接,需显式添加extern
关键字。
初始化要求
C++要求const
变量必须在声明时初始化,否则会编译错误。C语言允许const
变量稍后初始化,但未初始化的const
变量可能导致未定义行为。
数组大小声明
C++允许使用const
变量声明数组大小,且该变量被视为编译时常量表达式。C语言中,即使使用const
变量,数组大小仍需通过宏或字面量定义,const
变量在C中不被视为常量表达式。
// C++合法,C不合法
const int size = 10;
int arr[size];
指针与常量的关系
C和C++均支持指向常量的指针和常量指针,但类型检查严格性不同。C++禁止将非const
指针赋给const
指针(隐式转换),而C允许(可能引发警告)。
// C++报错,C允许(带警告)
int* p;
const int* cp = p;
与宏的替代关系
C++中const
可完全替代宏定义常量,支持类型检查和作用域规则。C语言中const
无法完全替代宏,例如在case
语句或位域长度中仍需使用宏。
C++中的扩展用法
C++允许const
用于类成员函数(const
成员函数),表明该函数不修改对象状态。C语言无此功能,因其不支持面向对象。
class Example {
public:void foo() const; // 合法C++,不修改成员变量
};
类型安全
C++中const
是类型系统的一部分,参与函数重载和模板推导。C语言中const
更多是编译期提示,缺乏严格的类型检查。
const和指针的区别
在C++中,const
和指针的关系涉及指针的指向和指向内容的可变性,主要通过const
的位置区分。以下是具体区别:
const修饰指针指向的内容
声明方式为const T*
或T const*
(两者等价),表示指针指向的内容不可修改,但指针本身可以重新指向其他地址。
int a = 10, b = 20;
const int* ptr = &a;
// *ptr = 30; // 错误:内容不可修改
ptr = &b; // 正确:指针可以指向其他地址
const修饰指针本身
声明方式为T* const
,表示指针本身不可修改(必须初始化),但指向的内容可以修改。
int a = 10, b = 20;
int* const ptr = &a;
*ptr = 30; // 正确:内容可修改
// ptr = &b; // 错误:指针不可重新指向
双const修饰
声明方式为const T* const
,表示指针本身和指向的内容均不可修改。
int a = 10, b = 20;
const int* const ptr = &a;
// *ptr = 30; // 错误:内容不可修改
// ptr = &b; // 错误:指针不可重新指向
区别总结
const T*
:内容只读,指针可变。T* const
:指针只读,内容可变。const T* const
:指针和内容均为只读。
举例
//哪些正确,哪些错误?
int a = 10, b = 20;
const int* p = &a; // p是指向常量的指针,不能通过p修改a的值
int* s1 = p; // 错误:无法将const int*隐式转换为int*
const int* s2 = p; // 正确:s2与p类型相同,均为指向常量的指针
int* const s3 = p; // 错误:s3是常量指针,但类型不匹配(无法舍弃const)
const int* const s4 = p; // 正确:s4是指向常量的常量指针,与p类型兼容
int a = 10, b = 20;
int* const p = &a;
int* s1 = p;
const int* s2 = p;
int* const s3 = p;
const int* const s4 = p;//可以编译通过
四、*和 &
*
int c = a * b;//乘法运算符
double* p = &a;//指针的修饰符
*p = 100.100;//解引用
&
a& b;//位与运算符
int* p = &c;//取地址
int& x = a;//引用,别名
注意事项:
当&在引用时,没有空引用;必须初始化;没有引用的引用;
左值引用:可以取地址的标识符是左值引用,&
右值引用:不能取地址的引用。内置类型所产生的右值都有常性,即只可读不可取,&&
const引用
const int &;
- 常引用或者万能引用
- 既可引用左值引用,又可引用右值引用
- 只可以去获取值,但不可以修改值
int const &;
引用作为形参代替指针
使用指针交换两个整形值。c语言实现:
void my_swap(int* a, int* b) {assert(a != NULL && b != NULL);int tmp = *a;*a = *b;*b = tmp;
}
int main() {int a = 10, b = 20;cout << "a = " << a << "b = " << b << endl;my_swap(&a, &b);cout << "a = " << a << "b = " << b << endl;return 0;
}
使用引用交换两个整形值。c++实现
不存在NULL引用,不需要判空,比指针安全。
void my_swap2(int &x, int &y) {int tmp = x;x = y;y = tmp;
}
int main() {int a = 10, b = 20;cout << "a = " << a << "b = " << b << endl;my_swap(&a, &b);cout << "a = " << a << "b = " << b << endl;return 0;
}
引用的其它格式
失效指针:失效指针(Dangling Pointer)是指向已释放或无效内存地址的指针。当指针指向的内存被释放后,指针本身未置空,仍保留原地址,此时访问该指针会导致未定义行为(如程序崩溃、数据错误等)。
失效指针的常见场景
动态内存释放后未置空
使用free
或delete
释放内存后,指针未设置为NULL
,导致后续访问失效内存。int *ptr = (int *)malloc(sizeof(int)); free(ptr); // ptr 成为失效指针 *ptr = 10; // 危险操作!
局部变量作用域结束
指针指向函数内的局部变量,函数返回后局部变量被销毁,指针变为失效。int *getLocalPointer() {int x = 5;return &x; // 返回局部变量的地址 } int *ptr = getLocalPointer(); // ptr 指向已释放的栈内存
对象生命周期结束
C++ 中对象被销毁后,指向其成员或自身的指针变为失效。class MyClass { public:int val; }; MyClass *obj = new MyClass(); delete obj; // obj 成为失效指针 obj->val = 42; // 未定义行为
避免失效指针的方法
释放后置空
释放内存后立即将指针设为NULL
或nullptr
,后续可通过判空避免误用。free(ptr); ptr = NULL; // 安全措施
使用智能指针(C++)
std::shared_ptr
或std::unique_ptr
自动管理内存生命周期,避免手动释放导致的失效问题。std::shared_ptr<int> ptr = std::make_shared<int>(42); // 无需手动释放,超出作用域后自动销毁
避免返回局部变量地址
若需返回指针,应使用动态分配内存或静态变量。int *getSafePointer() {int *x = (int *)malloc(sizeof(int));*x = 5;return x; // 返回堆内存地址 }
静态分析工具
使用 Valgrind、AddressSanitizer 等工具检测代码中的失效指针问题。
指针和解引用的区别
语法规则:
- 从语法规则上讲,指针变量存储某个实例(变量或对象)的地址;引用是某个实例的别名
- 程序为指针变量分配内存区域;而不为引用分配内存区域。
- 解引用是指针使用时要在前加“*”;引用可以直接使用。
- 指针变量的值可以发生改变,存储不同实例的地址;引用在定义时就被初始化,之后无法改变(不能是其他实例的引用)。
- 指针变量的值可以为空(NULL,nulptr);没有空引用。
- 指针变量作为形参时需要测试它的合法性(判空NULL);
- 引用不需要判空对指针变量使用“sizeof"得到的是指针变量的大小。对引用变量使用"sizeof"得到的是变量的大小。
- 理论上指针的级数没有限制;但引用只有一级。即不存在引用的引用,但可以有指针的指针。
- ++引用与++指针的效果不一样。例如就++操作而言会使指针变量指向下一个实体(变量或对象)的地址;而不是改变所指实体(变量对指针变量的操作,今或对象)的内容。
- 对引用的操作直接反应到所引用的实体(变量或对象)
- 不可以对函数中的局部变量或对象以引用或指针方式返回。
引用作为函数的返回值类型
不能对函数中的局部变量或对象以引用或指针的方式返回。
语法糖:
语法糖(Syntactic Sugar)指简化代码表达的语言特性,使代码更简洁易读。
五、缺省参数
- 缺省参数指在定义函数时为形参指定缺省值(默认值)。
- 这样的函数在调用时,对于缺省参数,可以给出实参值,也可以不给出参数值。如果给出实参,将实参传递给形参进行调用,如果不给出实参,则按缺省值进行调用。
- 缺省参数的函数调用:缺省实参并不一定是常量表达式,可以是任意表达式,甚至可以通过函数调用给出。如果缺省实参是任意表达式,则函数每次被调用时该表达式被重新求值。但表达式必须有意义;
- 缺省参数可以有多个,但所有缺省参数必须放在参数表的右侧,即先定义所有的非缺省参数,再定义缺省参数。这是因为在函数调用时,参数自左向右逐个匹配,当实参和形参个数不一致时只有这样才不会产生二义性。
- 习惯上,缺省参数在公共头文件包含的函数声明中指定,不要函数的定义中指定。
如果在函数的定义中指定缺省参数值,在公共头文件包含的函数声明中不能再次指定缺省参数值。缺省实参不一定必须是常量表达式可以使用任意表达式。 - 当缺省实参是一个表达式时在函数被调用时该表达式被求值。
- C语言不支持
1、缺省参数的语法规则
- 缺省参数在函数声明中指定,格式为
类型 参数名 = 默认值
。 - 若函数有多个参数,缺省参数必须从右向左连续定义。非缺省参数不能出现在缺省参数右侧。
void func(int a, int b = 10, int c = 20); // 正确
void func(int a = 5, int b, int c); // 错误:非缺省参数b在缺省参数a右侧
2、缺省参数的使用示例
#include <iostream>
using namespace std;// 函数声明时指定缺省参数
void printMessage(const string& msg = "Hello, World!") {cout << msg << endl;
}int main() {printMessage(); // 输出: Hello, World!printMessage("Hi!"); // 输出: Hi!return 0;
}
3、缺省参数的注意事项
声明与定义分离时:缺省参数仅在函数声明中指定,定义时不能重复指定。
// 声明(含缺省参数) void logError(const string& file, int line = -1);// 定义(不能重复缺省值) void logError(const string& file, int line) { /* ... */ }
作用域规则:同一作用域内,缺省参数只能指定一次。多次声明同一函数时,后续声明可为之前未指定缺省的参数添加默认值,但不能修改已存在的缺省值。
void foo(int x, int y = 10); // 首次声明 void foo(int x = 5, int y); // 合法:补充x的缺省值 void foo(int x = 8, int y); // 错误:试图修改x的缺省值
避免二义性:缺省参数可能导致函数重载冲突。
void bar(int a); void bar(int a, int b = 0); bar(10); // 错误:调用歧义
与函数指针兼容性:函数指针类型必须严格匹配参数列表,包含缺省参数信息。
缺省参数的典型应用场景
- 简化接口:为常用参数提供默认值,减少冗余调用代码。
- 扩展函数功能:通过逐步添加缺省参数扩展函数,避免破坏现有代码。
六、函数重载
函数重载的概念
函数重载(Function Overloading)是指在同一个作用域内,允许定义多个同名函数,但这些函数的参数列表(参数类型、数量或顺序)必须不同。编译器根据调用时提供的实参类型和数量,自动匹配最合适的函数版本。
函数重载的核心规则
如果两个函数的参数表相同,但返回类型不同,会被标记为编译错误:函数重复声明
参数表的比较过程与形参名无关
如果在两个函数的参数列表中,只有缺省实参不同,则第二个声明被视为第一个的重复声明
typedef名为现有的数据类型提供了一个替换名,它并没有创建一个新类型,因此,如果两个函数参数表的区别只在于一个使用了typedef,而另一个使用了与typedef相应的类型。则该参数表被视为相同的参数列表。
当一个形参类型有const或volatile修饰时,如果形参是按值传递方式定义,在识别函数声明是否相同时,并不考虑const和volatile修饰符.
当一个形参类型有const或volatile修饰时,如果形参定义指针或引用时,在识别函数声明是否相同时,就要考虑const和volatile修饰符.
注意函数调用的二义性;如果在两个函数的参数表中,形参类型相同,而形参个数不同,形参默认值将会影响函数的重载.
函数重载解析的步骤
- 确定函数调用考虑的重载函数的集合,确定函数调用中实参表的属性.
- 从重载函数集合中选择函数,该函数可以在(给出实参个数和类型)的情况下可以调用函数.
- 选择与调用最匹配的函数.
函数重载的注意事项
- 默认参数与重载的冲突:默认参数可能导致函数调用歧义。
void func(int a, int b = 0); void func(int a); func(10); // 编译错误:无法确定调用哪个版本
- 类型转换的影响:隐式类型转换可能干扰重载匹配。
void print(int x); void print(double x); print(3.14f); // 可能匹配double版本而非预期float
函数重载的应用场景
- 处理不同类型数据:如数学运算支持
int
、float
等。 - 简化接口设计:同名函数提供不同参数组合的调用方式。
- 构造函数重载:类通过不同参数列表实现多种初始化方式。