C++学习笔记(四)

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

 C++学习笔记(一)
        一、C++编译阶段※

        二、入门案例解析 

        三、命名空间详解

        四、C++程序结构

C++学习笔记(二)

        五、函数基础

         六、标识符

        七、数据类型

        补充:二进制相关的概念

                  sizeof 运算符简介

        补充:原码、反码和补码

C++学习笔记(三)

        补充:ASCII码表

        八、内存构成

                1、存分区及其功能      

                2、地址

                3、内存布局 ※​编辑

         补充:变量

                1、左值(lvalue)

                2、右值(rvalue)

                3、初始化不是赋值※

                4、使用 auto 自动推导类型(C++11 起)

                5、变量作用域

        九、指针基础 

         十、const关键字

                1、常变量

                2、指向常量的指针

                3、顶层与底层const

        十一、枚举类型 

目录

十二、 类型转换

1、隐式类型转换

2、显式类型转换

十三、define指令 

 十四、typedef关键字

 十五、运算符

1、算术运算符:

2、赋值运算符 

3、比较运算符 

4、逻辑运算符 

 5、位运算符

 6、移位运算符

7、三目运算符

8、sizeof运算符

 9、逗号运算符

10、优先级结合性


十二、 类型转换

1、隐式类型转换

定义:隐式类型转换是编辑器自动完成的类型转换,无需程序员显式声明。通常发生在以下c场景中:

  • 算术运算中不同类型的转换(如 int double 相加)
  • 赋值语句中类型不匹配(如 int 赋值给 double
  • 函数参数传递时类型不匹配
  • 控制语句(如 if while )中操作数类型不一致

特点

  • 自动执行:编译器根据规则自动完成转换
  • 可能丢失精度:例如将 double 转换为 int 时会截断小数部分
  • 提升优先级:小类型向大类型提升(如 char int double

当表达式中小类型与大类型一同运算时,小类型自动提升为大类型

常见场景:

  • 算术运算中的类型提升

        char c = 'A' ; // 1 字节
        int i = 100 ; // 4 字节
        double d = 3.14 ; // 8 字节
        double result = c + i + d ; // c int double, i double, d→ double
  • 函数参数传递
        void func ( double x );
        func ( 5 ); // 隐式转换: int double
  •  赋值语句
        int i = 3.14 ; // 隐式转换: double int (截断为 3

注意事项

  • 数据丢失风险:隐式转换可能导致精度丢失或溢出。
  • 不明确性:如果存在多个用户定义的转换函数,可能导致编译器无法确定使用哪一个(如多重继承中的转换歧义)。
  • 安全问题:例如将 const 对象转换为非 const (需 const_cast )时修改值可能导致未定义行为。

2、显式类型转换

定义 : 显式类型转换是程序员 主动指定 的类型转换,通过语法明确告知编译器如何转换类型。C++ 提供了 C 风格转换 C++ 风格的四种转换运算符
特点
        手动控制:程序员明确指定转换方式
        更安全 C++ 风格转换(如 static_cast )具有更强的类型检查
        适用复杂场景:如去除 const 属性、多态类型检查等
C 风格转换
         //格式
        数据类型1 变量名 = ( 数据类型 1 ) 数值、变量名或表达式 ;
        short s = short (i+d);        //i是int类型,d是double类型
        或数据类型1 变量名 = ( 数据类型 1 )( 数值、变量名或表达式 );
        //案例
        double d = 3.14 ;
        int i = ( int ) d ;
C++ 风格转换运算符
运算符 用途 示例
static_cast<T> 基本类型转换、类层次结构的向上/向下转换(已知安全时) double d = staatic_cast<double>(5);
dynamic_cast<T> 多态类型检查,运行时安全转换(向下转换) Derived* d = dynamic_cast<Derived*>(base);
const_cast<T> 添加或去除const/volatile属性 int* p = const_cast<int*>(&c)
reinterpret_cast<T> 低级别重新解释位模式,危险但灵活(如指针与证书转换) uintptr_t  addr = reinterpret_cast<uintptr_t>(p);
示例
①  基本类型转换【建议 C 风格】
        double d = 3.14 ;
        int i = static_cast < int > ( d ); // 显式转换: double int (截断为3)
②  去除 const 属性【掌握】        
        const int c = 42 ;
        int* p = const_cast < int* > ( & c ); // 去除 const
        * p = 100 ; // 未定义行为( c const
③  指针与整数转换【了解、应用少】
        int i = 42 ;
        int* p = & i ;
        uintptr_t addr = reinterpret_cast < uintptr_t > ( p ); // 指针转整数
        无符号整型数
类层次结构转换【后续学习】
注意事项
       C 风格转换的风险缺乏类型检查,可能隐藏潜在错误(如将 double* 强制转换为 int*
       优先使用 C++ 风格转换 :更安全、语义更清晰,例如 dynamic_cast 的运行时检查避免了无效的向下转换。
两种转换对比
特性 隐式转换 显式转换
触发方式 编译器自动完成 程序员手动指定(如 static_castdynamic_cast
安全性 可能隐含风险(如数据丢失、精度降低) 更安全(类型检查明确,避免意外行为)
适用场景 简单、安全的类型转换(如 intdouble 复杂场景(如多态转换、去除 const 修饰)
代码可读性 简洁,但可能隐藏实际逻辑 明确表达意图,提高可维护性
示例 int i = 3.14;(编译器截断小数部分) int i = static_cast<int>(3.14);(明确截断操作)

关键说明

  • 隐式转换:由编译器在赋值、函数传参等场景自动执行,适用于无精度损失或逻辑清晰的转换(如数值提升)。
  • 显式转换:需通过 static_castreinterpret_cast 等关键字手动触发,适用于需要强制类型检查或高风险操作(如指针类型转换)。
  使用建议:
        1. 隐式转换的适用场景
        简单的基本类型转换(如 int double )。
        无风险的赋值或算术运算(如 char + int )。
        避免可能导致数据丢失的隐式转换(如 double int )。
        2. 显式转换的适用场景
        类层次结构中的安全转换( dynamic_cast )。
        需要明确去除 const 属性( const_cast )。
        低级别操作(如指针与整数转换, reinterpret_cast )。
        优先使用 C++ 风格转换(如 static_cast )替代 C 风格转换。
        3. 避免陷阱
  • 隐式转换的不明确性:如用户定义的转换函数冲突时,需明确指定转换方式。
  • 显式转换的风险reinterpret_cast const_cast 需谨慎使用,避免未定义行为。
总结
  • 隐式转换:简洁但需警惕风险,适用于简单、安全的类型转换场景
  • 显式转换:更安全且语义明确,适用于复杂或需要明确意图的转换
最佳实践
  • 优先使用 C++ 风格转换(如 static_cast dynamic_cast );
  • 在可能丢失数据或存在歧义时,显式转换能提高代码的健壮性;
  • 避免过度依赖隐式转换,尤其在涉及用户定义类型时。
  • 通过合理使用隐式和显式转换,可以编写更高效、安全的 C++ 代码!

十三、define指令 

        在C++ 中, #define 是一个预处理指令,用于定义宏。宏可以用来定义常量值、函数宏(类似于函数的宏),以及进行条件编译等操作。 #define 指令是在编译之前由预处理器处理的,这意味着它们不是C++ 语言的一部分,而是C预处理器的功能。
        性能更好,在编译之前由预处理器处理。
        C++的宏只做文本替换
1、基本语法
        #define 标识符 替换文本
  • 标识符:你希望定义的名字
  • 替换文本:当预处理器遇到这个标识符时,会用这里的替换文本代替它

2、具体应用:

//1.定义一个常量值【掌握】、
// 预处理器会在编译前将代码中的 PI 全部替换成 3.14159
#define PI 3.14159
//2.定义带参数的宏,这被称为函数宏【了解】
#define SQUARE(x) ((x)*(x))
//警告:由于函数宏只是简单替换,直接使用变量名作为参数可能导致意外结果,例如
int a = 5;
int b = SQUARE(a++); //变成 ((a++)*(a++)), 可能导致未定义行为
//3.#define 经常与条件编译指令一起使用,如 #ifdef, #ifndef, #else,
#elif, 和 #endif,以根据是否定义了某个宏来决定编译哪些代码【掌握】
#define DEBUG
#ifdef DEBUG
std::cout << "Debugging is enabled" << std::endl;
#else
std::cout << "Debugging is disabled" << std::endl;
#endif
//4.避免头文件被多次包含导致重复定义【掌握】
#ifndef MY_HEADER_FILE_H
#define MY_HEADER_FILE_H
// 头文件内容
#endif // MY_HEADER_FILE_H
//5.用宏简化复杂的表达式或重复的代码块【了解即可】
#define CHECK_POINTER(p) if (p == nullptr) { std::cerr << "Null
pointer found!" << std::endl; exit(1); }
//具体使用
CHECK_POINTER(myPointer);

//1. 定义一个常量值【掌握】、
// 预处理器会在编译前将代码中的 PI 全部替换成 3.14159
#define PI 3.14159
//2. 定义带参数的宏,这被称为函数宏【了解】
#define SQUARE(x) ((x)*(x))
// 警告:由于函数宏只是简单替换,直接使用变量名作为参数可能导致意外结果,例如
int a = 5 ;
int b = SQUARE ( a ++ ); // 变成 ((a++)*(a++)), 可能导致未定义行为
//3.#define 经常与条件编译指令一起使用,如 #ifdef, #ifndef, #else,
#elif, #endif ,以根据是否定义了某个宏来决定编译哪些代码【掌握】
#define DEBUG
#ifdef DEBUG
std::cout << "Debugging is enabled" << std::endl ;
#else
std::cout << "Debugging is disabled" << std::endl ;
#endif
//4. 避免头文件被多次包含导致重复定义【掌握】
#ifndef MY_HEADER_FILE_H
#define MY_HEADER_FILE_H
// 头文件内容
#endif // MY_HEADER_FILE_H
//5. 用宏简化复杂的表达式或重复的代码块【了解即可】
#define CHECK_POINTER(p) if (p == nullptr) { std::cerr << "Null
pointer found!" << std::endl; exit(1); }
// 具体使用
CHECK_POINTER ( myPointer );
3、注意事项
  • 无类型检查:因为宏只是简单的文本替换,所以不会进行类型检查,可能会引入难以发现的错误。
  • 括号的重要性:在定义带有参数的宏时,给参数和整个宏加括号是非常重要的,避免优先级问题。
  • 调试困难:由于宏在预处理阶段就被替换了,所以在调试时看到的是替换后的代码,增加了调试难度。
  • 命名冲突:宏名应该尽量唯一,以免与变量名或其他宏名冲突。
        虽然宏非常强大,但在现代C++ 中,对于常量值推荐使用 const 或者 constexpr ,对于类似函数的功能推荐使用内联函数或模板函数,这样可以获得更好的类型安全性和更清晰的代码结构。然而,在某些情况下,宏仍然是不可替代的,比如条件编译。

 十四、typedef关键字

        在C++ 中, typedef 关键字用于为现有的数据类型创建一个新的名字(别名)。这可以使得代码更加清晰易读,尤其是当处理复杂的数据结构如指针、数组、函数指针或者模板实例时。 typedef 并不会创建新的类型,它只是为现有类型提供了一个新的名称。
1、语法:
                        typedef existing_type new_type_name;
2、应用场景
//1. 为基本类型创建别名,可以使代码更具可读性或适应特定领域的术语
typedef unsigned long ulong ;
ulong bigNumber = 1234567890UL ;
//2. 简化复杂类型的声明
typedef char * ptr_to_char ;
ptr_to_char a , b , c ;
// 注意:一定要区分 typedef #define 的区别
#define d_ptr_char char *
d_ptr_char a , b , c ;
//3. 结构体类型 类型起别名
typedef struct {
int id ;
char name [ 100 ];
} Person ;
Person person1 ;

补充内容:typedef 对于定义和使用函数指针特别有用!

#include <iostream>

// 定义一个新类型 fnPtr,表示返回值为 int,接受两个 int 参数的函数指针
typedef int (*fnPtr)(int, int);

// 示例函数
int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

int main() {
    fnPtr operation = add;
    std::cout << "Add: " << operation(5, 3) << std::endl;

    operation = subtract;
    std::cout << "Subtract: " << operation(5, 3) << std::endl;

    return 0;
}
 
注意:避免混淆 :虽然 typedef 可以让代码看起来更简洁,但过度使用或命名
不当可能导致混淆。

 十五、运算符

        运算符:对字面值常量或变量进行操作的 符号 ,也称 操作符
        表达式: 运算符 把字面值常量或变量连接起来的式子(符合 C++ 语法),就称之为表达式。
例如:
        int a = 10 ;
        int b = 20 ;
        int c = a + b ; // + 属于是运算符(操作符)
        // a + b 叫做表达式
        c = c + 30 ; // c + 30 也叫做表达式
分类:

1、算术运算符:

操作符 作用 例子
+ 两个值相加 int a = 1+1;
- 两个数字相减 int a = 1-1;
* 两个数字相乘 int a = 1*1;
/ 两个数字相除 int a = 1/1;
% 两个数字取余 int a = 5%2;

 %求余运算符

示例

#include<iostream>
using namespace std;
int main()
{
    int n = 13 % 5;
    cout << "n: " << n << endl; // ?
    n = -13 % 5;
    cout << "n: " << n << endl; // ?
    n = 13 % -5;
    cout << "n: " << n << endl; // ?
    n = -13 % -5;
    cout << "n: " << n << endl; // ?
}
输出结果:3、-3、3、-3
结论:求余运算,结果符号只跟左操作数的符号有关

★自增自减
        ++为自增, -- 为自减,两者使用方式类似,下面重点介绍 ++
        自增或自减有两种使用方式: 变量名 ++ ++ 变量名;
使用方式也有 2 种,具体如下:
1 )单独使用 ,目的是对变量进行自增或自减,上述 2 种方式作用相同
2 )作为表达式使用 ,两者作用有明显区别
展示:

★前置自增与后置自增的区别★

++a(前置自增)和a++(后置自增)的主要区别在于自增操作的时机表达式的返回值

前置自增++a先将变量a的值加1,然后返回自增后的值。后置自增a++则先返回变量a的当前值,再将a的值加1。

代码示例

int a = 5;
int b = ++a; // a先自增为6,b被赋值为6
int c = a++; // c被赋值为6(a的当前值),之后a自增为7

运行结果分析

  • 执行int b = ++a后,a的值为6,b的值也为6。
  • 执行int c = a++后,c的值为6(自增前的a值),而a的值变为7。

使用场景建议

  • 若需立即使用自增后的值(如循环条件或数组索引),优先选用++a
  • 若需保留当前值后再自增(如赋值或返回值场景),选用a++

2、赋值运算符 

常见赋值操作符及其作用

操作符 作用 例子
= 赋值操作符 int a = 1; int x = 0;
+= 变量和右操作数相加,然后赋值 int a = 5; a += 2; // a = a + 2;
-= 变量和右操作数相减,然后赋值 int a = 5; a -= 2; // a = a - 2;
*= 变量和右操作数相乘,然后赋值 int a = 1; a *= 2; // a = a * 2;
/= 变量和右操作数相除,然后赋值 int a = 2; a /= 2; // a = a / 2;
%= 变量和右操作数求余,然后赋值 int a = 5; a %= 2; // a = a % 2;

3、比较运算符 

操作符及作用示例表

操作符 作用 例子
> 比较是否大于 1 > 0
>= 比较是否大于等于 1 >= 0
< 比较是否小于 1 < 2
<= 比较是否小于等于 1 <= 2
== 判断两个操作数是否相等 bool f = (a == b);
!= 判断两个操作数是否不相等 bool f = (a != b);

4、逻辑运算符 

逻辑运算符功能及示例表

操作符 作用 例子
&& 与运算,带逻辑短路 a && bab为表达式,两者均为true时结果为true,否则false
& 与运算(无短路) a & bab均为true时结果为true,否则false
|| 或运算,带逻辑短路 a || bab有一个为true时结果为true,否则false
| 或运算(无短路) a | bab有一个为true时结果为true,否则false
! 非运算(取反) !aatrue时结果为false,反之为true
^ 异或运算(相同为假,不同为真) a ^ bab一个为false、另一个为true时结果为true

关键差异说明

  • 短路特性&&和||会在第一个操作数确定结果后跳过后续计算,而&和|会强制计算所有操作数。
  • 异或逻辑:仅在操作数不同时返回true,适用于需要严格区分两种状态的场景。

 5、位运算符

        位运算符直接对整数在内存中的二进制位进行操作,适用于高效处理底层数据或优化性能的场景。

操作符与作用说明

操作符 作用 例子及说明
& 与运算 1 & 1 = 11 & 0 = 00 & 1 = 00 & 0 = 0(全1为1,否则为0)
| 或运算 1|1=1、1|0=1、0|1=1,0|0=0
^ 异或运算 1 ^ 1 = 00 ^ 0 = 01 ^ 0 = 10 ^ 1 = 1(相同为0,不同为1)
~ 取反运算 ~0 = 1~1 = 0(二进制位直接反转)

案例:
#include <iostream>

using namespace std;

int main()
{
    //2.实例化对象
    cout << "input two num: ";
    //n1: 10 0 0(23) 0000 1010
    //n2: 3 0 0(23) 0000 0011
    int n1, n2;
    cin >> n1 >> n2;
    //&: 1&1 == 1 0&? == 0
    // 0 0(23) 0000 1010
    //& 0 0(23) 0000 0011
    // 0 0(23) 0000 0010 ==> 2
    int value = n1 & n2;
    cout << "n1 & n2: " << value << endl;
    // 0 0(23) 0000 1010
    // 0 0(23) 0000 0011
    // 0 0(23) 0000 1011 ==> 11
    value = n1 | n2;
    cout << "n1 | n2: " << value << endl;
    // 0 0(23) 0000 1010
    //^ 0 0(23) 0000 0011
    // 0 0(23) 0000 1001 ==> 9
    value = n1 ^ n2;
    cout << "n1 ^ n2: " << value << endl;
    // 0000 0000 0000 1010
    short num = 10;
    //~ 0000 0000 0000 1010
    // 1111 1111 1111 0101 ==> -?
    // 原码 <-> 反码 <-> 补码
    // 补: 1111 1111 1111 0101
    // 反:补码-1
    // 1111 1111 1111 0100
    // 原: 反码保留符号位,其他位取反
    // 1000 0000 0000 1011
    // 结果: -11
    value = ~num;
    cout << "~num: " << value << endl;
}

案例2
^特殊用法,不使用中间变量的前提下,可以交换2个变量的值

#include <iostream>
using namespace std ;
// 定义方法,交换两个整形数的值 【先用,方法具体讲解会在本章最后一个内容中 讲解】
/* 修饰符 返回值类型 函数名 ( 形式参数列表 ) {
         方法体语句 ;
  }*/
void swap ( int a , int b )
{
        cout << " 交换前: a: " << a << ", b: " << b << endl ;
        int t = a ;
        a = b ;
        b = t ;
        cout << " 交换后: a: " << a << ", b: " << b << endl ;
}
// 不借助第三个变量,完成 2 个数的交换
void swap2 ( int a , int b )
{
        cout << " 交换前: a: " << a << ", b: " << b << endl ;
        // 一个数 异或 另一个数 2 次,结果是自己
        a = a ^ b ;
        b = a ^ b ; // a^b^b ==> a
        a = a ^ b ; // a^b^a ==> b;
        cout << " 交换后: a: " << a << ", b: " << b << endl ;
}
// 传地址调用
void swap3 ( int * pa , int * pb )
{
        * pa = * pa ^ * pb ;
        * pb = * pa ^ * pb ;
        * pa = * pa ^ * pb ;
}
// 测试代码
int main ()
{
        int x = 10 ;
        int y = 20 ;
        swap ( x , y );
        cout << "------after swap: -----" << endl ;
        cout << " 交换后: x: " << x << ", y: " << y << endl ;
        cout << "-----------------------" << endl ;
        swap2 ( x , y );
        cout << "------after swap2: -----" << endl ;
        cout << " 交换后: x: " << x << ", y: " << y << endl ;
        cout << "-----------------------" << endl ;
        swap3 ( & x , & y );
        cout << "------after swap3: -----" << endl ;
        cout << " 交换后: x: " << x << ", y: " << y << endl ;
}
运行效果

 
原理图
重点理解:
  • 传值调用:当使用传值调用时,函数会接收参数的一个副本。也就是说,在调用函数时,实参的值会被复制给形参,函数内部对形参的所有操作都不会影响到原始数据;
  • 传址调用:通过传递参数的地址给函数,函数能够直接访问和修改这些参数的原始数据。这通常通过指针来实现。

练习:使用位操作符,对变量a10)进行某一位置0或置1操作

思路:

检查某一位的值

如果需要检查变量a的第n位是0还是1,可以使用按位与(&)操作符。具体步骤如下:

  1. 创建一个掩码,该掩码只有第n位为1,其余位为0。
  2. 将变量a与掩码进行按位与操作。
  3. 如果结果为0,则第n位为0;否则为1。

对变量a(值为10)的二进制表示

        十进制10的二进制为00001010(假设为8位)。

置1操作

要将变量a的第n位置1,可以使用按位或(|)操作符。具体步骤如下:

  1. 创建一个掩码,该掩码只有第n位为1,其余位为0。可以通过将1左移n位来实现。
  2. 将变量a与掩码进行按位或操作。这样,无论第n位原来的值是什么,都会被置1。

        将第n位置1(低位从0开始计数),使用按位或操作:

a = a | (1 << n);

例如将第3位置1:

int a = 10;
a = a | (1 << 3); // 结果:00001010 | 00001000 = 00011010(十进制26)

置0操作

要将变量a的第n位置0,可以使用按位与(&)操作符。具体步骤如下:

  1. 创建一个掩码,该掩码只有第n位为0,其余位为1。可以通过将1左移n位后取反来实现。
  2. 将变量a与掩码进行按位与操作。这样,无论第n位原来的值是什么,都会被置0。

将第n位置0,需结合按位与和取反操作:

a = a & ~(1 << n);

例如将第1位置0:

int a = 10;
a = a & ~(1 << 1); // 结果:00001010 & 11111101 = 00001000(十进制8)

完整示例代码

#include <iostream>
using namespace std;

int main() {
    int a = 10;
    
    // 将第3位置1
    a = a | (1 << 3);
    cout << "After setting bit 3: " << a << endl;
    
    // 将第1位置0
    a = a & ~(1 << 1);
    cout << "After clearing bit 1: " << a << endl;
    
    return 0;
}

输出结果

After setting bit 3: 26
After clearing bit 1: 24

注意事项

  • 位计数从0开始(最低位为第0位)。
  • 操作前需确保移位范围不超过变量位数(如int通常为32位)。
  • 对无符号类型操作更安全,避免符号位影响。

逻辑运算符与位运算符的关键差异

  1. 操作对象

    • 逻辑运算符:布尔值(如条件判断)。
    • 位运算符:整数(如掩码操作、硬件编程)。
  2. 短路行为

    • 逻辑 || 和 && 可能跳过右侧计算。
    • 位运算符始终计算两侧操作数。
  3. 结果类型

    • 逻辑运算符返回 true/false
    • 位运算符返回整数。
  4. 符号差异

    • 逻辑运算符用 ||&&
    • 位运算符用单字符 |&

 6、移位运算符


        移位运算符是计算机编程中用于对二进制数进行位操作的运算符,主要分为左移运算符右移运算符。它们直接操作数据的二进制表示,适用于性能敏感的底层开发(如嵌入式系统、图形处理等)。

        如果要进行移位操作, 则需要先获取操作数的二进制形式(补码) ,然后按位进行操作。
操作符 作用 例子
>> 算术右移位运算,也叫做带符号的右移运算 8 >> 1
<< 左移位运算 8 << 1
右移 >>   低位抛弃,高位补符号位的值
#include < iostream >
using namespace std ;
int main ()
{
        // 0 0(23) 0000 1010
        int a = 10 ;
        cout << "a: " << a << endl ;
        // 0 0(23) 0000 1010
        // 00 0(23) 000 0101 [0] ==> 5
        int b = a >> 1 ;
        cout << "a >> 1: " << b << endl ;
        // 000 0(23) 0000 10 ==> 2
        b = a >> 2 ;
        cout << "a >> 2: " << b << endl ;
        // 负数移位操作
        a = - 10 ;
        // 获取 -10 的二进制补码:
        // 原 1 0(23) 0000 1010
        // 反 1 1(23) 1111 0101
        // 补 1 1(23) 1111 0110
        // 运算 : 111 1(23) 1111 01 [10]
        b = a >> 2 ; // 高位补 1
        // 结果推导:补 1 1(23) 1111 1101
        // - 1
        // 反 1 1(23) 1111 1100
        // 保留符号位,其他位取反
        // 原 1 0(23) 0000 0011
        // 结果: -3
        cout << "-10 >> 2: " << b << endl ;
}
左移 <<  高位抛弃,低位补0
#include < iostream >
using namespace std ;
int main ()
{
        int a = 10 ;
        cout << "a: " << a << endl ; // 10
        int b = a << 1 ;
        cout << "a << 1: " << b << endl ; // 20
        b = a << 2 ;
        cout << "a << 2: " << b << endl ; // 40
        b = a << 3 ;
        cout << "a << 3: " << b << endl ; // 80
        //结果:每左移 1 位,等同 == ( * 2)
        a = - 10 ;
        b = a << 2 ;
        // -10补码: 1 1(23) 1111 0110
        // 左移 2 位,结果:
        // 1(22) 1111 011000
        // 即: 1 1(23) 1101 1000
        // -1得反码: 1 1(23) 1101 0111
        // 保留符号位,其他为取反,得原码:
        // 原 : 1 0(23) 0010 1000 ==> -40
        cout << "a << 4: " << b << endl ;
        b = a << 1 ;
        cout << "a << 1: " << b << endl ; //-20
        // 如果左移位数太多,超出了数值表示范围,如何处理?
        // C++20及以后规定:数值 << n 等同 数值 << (n% 当前数值所占比特位数)
        b = a << 33 ;
        cout << "a << 33: " << b << endl ; //
        cout << "a << (33%32): " << b << endl ; //
}
小结
  • 正整数每左移1位,等同于 *2
  • C++20及以上规范:数值左移n(移动后将超出数值最高位),等同 数值<< (n%当前值所属类型所占比特位)
  • C++20之前的规则:对负数进行左移属于未定义行为Undefined Behavior),结果不可预测

7、三目运算符

三目运算符也称条件运算符。
格式

        (关系表达式) ? 表达式1 : 表达式2;

关系表达式成立,返回表达式 1 ,否则返回表达式 2

案例:

#include < iostream >
using namespace std ;
// 从键盘录入 2 个整数,比较大小
int main ()
{
        cout << "input two num: " ;
        int a , b ;
        cin >> a >> b ;
        int max = ( a > b ) ? a : b ;
        int min = ( a < b ) ? a : b ;
        cout << "max: " << max << endl ;
        cout << "min: " << min << endl ;
}

8、sizeof运算符

        在C++中, sizeof 是一个非常重要的操作符,用于计算变量或数据类型在内存中占用的字节数,其返回值是 size_t 类型(无符号整数),定义在<cstddef> <stddef.h> 头文件中。sizeof用于计算数据类型或对象在内存中所占的字节数。它可以在编译时确定结果,返回一个size_t类型的无符号整数。

size_t size = sizeof ( int ); // size 的类型为 size_t

使用方式:

计算数据类型大小
直接对数据类型使用sizeof,例如:

size_t size_int = sizeof(int);  // 返回int类型的字节数(通常为4)
size_t size_double = sizeof(double);  // 返回double类型的字节数(通常为8)

计算变量或表达式大小
可以作用于变量或表达式,例如:

int arr[10];
size_t size_arr = sizeof(arr);  // 返回数组总字节数(10 * sizeof(int))
size_t size_ptr = sizeof(&arr); // 返回指针的字节数(通常为8或4)

注意事项

  • sizeof在编译时求值,不会实际运行表达式。例如:
int x = 10;
size_t size_expr = sizeof(x++);  // x++不会执行,仅计算x的类型大小
  • 对数组名使用时返回数组总字节数,但对指针使用时返回指针本身的大小(而非指向的数据大小):
char str[] = "hello";
char *p = str;
size_t size_str = sizeof(str);  // 返回6(包含'\0')
size_t size_p = sizeof(p);      // 返回指针大小(通常为8或4)

常见用途

  • 动态内存分配时计算所需空间:
int *ptr = malloc(10 * sizeof(int));
  • 检查平台特定类型的大小(如long在不同系统可能为4或8字节)。
  • 计算结构体或类的大小(需注意内存对齐的影响)。

sizeof是理解和优化内存使用的重要工具,尤其在处理底层代码或跨平台开发时。

注意事项
1 )编译时计算 sizeof 的值在 编译时 确定,不会在运行时计算
        int n = 10 ;
        char arr [ n ]; // C99 支持动态数组
        cout << sizeof ( arr ); // 返回 10 (依赖编译器是否支持 C99
2 )数组长度计算 ,通过 sizeof 计算数组长度【后续数组章节重点讲解】
        int arr [] = { 1 , 2 , 3 , 4 , 5 };
        int len = sizeof ( arr ) / sizeof ( arr [ 0 ]); // 正确: 5

 9、逗号运算符

        在C++ 中, 逗号运算符 , )和 逗号表达式 是语言中一个独特且强大的特性,允许开发者在单个表达式中按顺序执行多个操作,并返回最后一个操作的结果。
        作用:将多个表达式按顺序执行,并返回最后一个表达式的值。
语法:
        ( 表达式 1 , 表达式 2 , ..., 表达式 N )
行为规则:
1. 顺序执行 :从左到右依次计算每个表达式。
2. 结果值 :整个逗号表达式的值是 最后一个表达式的值
3. 副作用:中间表达式的副作用(如变量修改)会保留,但它们的值会被丢弃。
逗号运算符的优先级-> 优先级最低
逗号运算符的优先级低于所有其他运算符(如赋值、算术运算等)

10、优先级结合性

运算符优先级:决定了表达式中多个运算符的计算顺序。优先级高的运算符会先于优先级低的运算符执行。

结合性:当多个相同优先级的运算符连续出现时,结合性决定了它们的计算方(从左到右或从右到左)。


网站公告

今日签到

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