【C++】入门阶段

发布于:2025-07-20 ⋅ 阅读:(13) ⋅ 点赞:(0)

一、初始化

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;   // 错误:指针不可重新指向

区别总结

  1. const T*:内容只读,指针可变。
  2. T* const:指针只读,内容可变。
  3. 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)是指向已释放或无效内存地址的指针。当指针指向的内存被释放后,指针本身未置空,仍保留原地址,此时访问该指针会导致未定义行为(如程序崩溃、数据错误等)。

失效指针的常见场景

  1. 动态内存释放后未置空
    使用 freedelete 释放内存后,指针未设置为 NULL,导致后续访问失效内存。

    int *ptr = (int *)malloc(sizeof(int));
    free(ptr);  // ptr 成为失效指针
    *ptr = 10;  // 危险操作!
    
  2. 局部变量作用域结束
    指针指向函数内的局部变量,函数返回后局部变量被销毁,指针变为失效。

    int *getLocalPointer() {
        int x = 5;
        return &x;  // 返回局部变量的地址
    }
    int *ptr = getLocalPointer();  // ptr 指向已释放的栈内存
    
  3. 对象生命周期结束
    C++ 中对象被销毁后,指向其成员或自身的指针变为失效。

    class MyClass {
    public:
        int val;
    };
    MyClass *obj = new MyClass();
    delete obj;     // obj 成为失效指针
    obj->val = 42;  // 未定义行为
    

避免失效指针的方法

  1. 释放后置空
    释放内存后立即将指针设为 NULLnullptr,后续可通过判空避免误用。

    free(ptr);
    ptr = NULL;  // 安全措施
    
  2. 使用智能指针(C++)
    std::shared_ptrstd::unique_ptr 自动管理内存生命周期,避免手动释放导致的失效问题。

    std::shared_ptr<int> ptr = std::make_shared<int>(42);
    // 无需手动释放,超出作用域后自动销毁
    
  3. 避免返回局部变量地址
    若需返回指针,应使用动态分配内存或静态变量。

    int *getSafePointer() {
        int *x = (int *)malloc(sizeof(int));
        *x = 5;
        return x;  // 返回堆内存地址
    }
    
  4. 静态分析工具
    使用 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、缺省参数的注意事项

  1. 声明与定义分离时:缺省参数仅在函数声明中指定,定义时不能重复指定。

    // 声明(含缺省参数)
    void logError(const string& file, int line = -1);
    
    // 定义(不能重复缺省值)
    void logError(const string& file, int line) { /* ... */ }
    
  2. 作用域规则:同一作用域内,缺省参数只能指定一次。多次声明同一函数时,后续声明可为之前未指定缺省的参数添加默认值,但不能修改已存在的缺省值。

    void foo(int x, int y = 10);  // 首次声明  
    void foo(int x = 5, int y);   // 合法:补充x的缺省值  
    void foo(int x = 8, int y);   // 错误:试图修改x的缺省值  
    
  3. 避免二义性:缺省参数可能导致函数重载冲突。

    void bar(int a);  
    void bar(int a, int b = 0);  
    bar(10);  // 错误:调用歧义  
    
  4. 与函数指针兼容性:函数指针类型必须严格匹配参数列表,包含缺省参数信息。

缺省参数的典型应用场景

  • 简化接口:为常用参数提供默认值,减少冗余调用代码。
  • 扩展函数功能:通过逐步添加缺省参数扩展函数,避免破坏现有代码。

六、函数重载

 

     函数重载的概念

函数重载(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  
    

函数重载的应用场景

  • 处理不同类型数据:如数学运算支持intfloat等。
  • 简化接口设计:同名函数提供不同参数组合的调用方式。
  • 构造函数重载:类通过不同参数列表实现多种初始化方式。

 

 

 


网站公告

今日签到

点亮在社区的每一天
去签到