C++学习笔记(一)
一、C++编译阶段※二、入门案例解析
三、命名空间详解
四、C++程序结构
五、函数基础
六、标识符
七、数据类型
补充:二进制相关的概念
sizeof 运算符简介
补充:原码、反码和补码
补充:ASCII码表
八、内存构成
1、存分区及其功能
2、地址
3、内存布局 ※编辑
补充:变量
1、左值(lvalue)
2、右值(rvalue)
3、初始化不是赋值※
4、使用 auto 自动推导类型(C++11 起)
5、变量作用域
九、指针基础
十、const关键字
1、常变量
2、指向常量的指针
3、顶层与底层const
十一、枚举类型
目录
十二、 类型转换
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_cast
、dynamic_cast
)安全性 可能隐含风险(如数据丢失、精度降低) 更安全(类型检查明确,避免意外行为) 适用场景 简单、安全的类型转换(如 int
→double
)复杂场景(如多态转换、去除 const
修饰)代码可读性 简洁,但可能隐藏实际逻辑 明确表达意图,提高可维护性 示例 int i = 3.14;
(编译器截断小数部分)int i = static_cast<int>(3.14);
(明确截断操作)关键说明
- 隐式转换:由编译器在赋值、函数传参等场景自动执行,适用于无精度损失或逻辑清晰的转换(如数值提升)。
- 显式转换:需通过
static_cast
、reinterpret_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 DEBUGstd::cout << "Debugging is enabled" << std::endl ;#elsestd::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 << "Nullpointer 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 && b
,a
、b
为表达式,两者均为true
时结果为true
,否则false
&
与运算(无短路) a & b
,a
、b
均为true
时结果为true
,否则false
||
或运算,带逻辑短路 a || b
,a
或b
有一个为true
时结果为true
,否则false
|
或运算(无短路) a | b
,a
或b
有一个为true
时结果为true
,否则false
!
非运算(取反) !a
,a
为true
时结果为false
,反之为true
^
异或运算(相同为假,不同为真) a ^ b
,a
和b
一个为false
、另一个为true
时结果为true
关键差异说明
- 短路特性:
&&
和||会在第一个操作数确定结果后跳过后续计算,而&
和|会强制计算所有操作数。- 异或逻辑:仅在操作数不同时返回
true
,适用于需要严格区分两种状态的场景。
5、位运算符
位运算符直接对整数在内存中的二进制位进行操作,适用于高效处理底层数据或优化性能的场景。
操作符与作用说明
操作符 作用 例子及说明 &
与运算 1 & 1 = 1
,1 & 0 = 0
,0 & 1 = 0
,0 & 0 = 0
(全1为1,否则为0)| 或运算 1|1=1、1|0=1、0|1=1,0|0=0 ^ 异或运算 1 ^ 1 = 0
,0 ^ 0 = 0
,1 ^ 0 = 1
,0 ^ 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 ==> aa = 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 ;}运行效果
![]()
原理图
重点理解:
- 传值调用:当使用传值调用时,函数会接收参数的一个副本。也就是说,在调用函数时,实参的值会被复制给形参,函数内部对形参的所有操作都不会影响到原始数据;
- 传址调用:通过传递参数的地址给函数,函数能够直接访问和修改这些参数的原始数据。这通常通过指针来实现。
练习:使用位操作符,对变量a(10)进行某一位置0或置1操作
思路:
检查某一位的值
如果需要检查变量a的第n位是0还是1,可以使用按位与(&)操作符。具体步骤如下:
- 创建一个掩码,该掩码只有第n位为1,其余位为0。
- 将变量a与掩码进行按位与操作。
- 如果结果为0,则第n位为0;否则为1。
对变量a(值为10)的二进制表示
十进制10的二进制为
00001010
(假设为8位)。置1操作
要将变量a的第n位置1,可以使用按位或(|)操作符。具体步骤如下:
- 创建一个掩码,该掩码只有第n位为1,其余位为0。可以通过将1左移n位来实现。
- 将变量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,可以使用按位与(&)操作符。具体步骤如下:
- 创建一个掩码,该掩码只有第n位为0,其余位为1。可以通过将1左移n位后取反来实现。
- 将变量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位)。- 对无符号类型操作更安全,避免符号位影响。
逻辑运算符与位运算符的关键差异
操作对象
- 逻辑运算符:布尔值(如条件判断)。
- 位运算符:整数(如掩码操作、硬件编程)。
短路行为
- 逻辑
||
和&&
可能跳过右侧计算。- 位运算符始终计算两侧操作数。
结果类型
- 逻辑运算符返回
true
/false
。- 位运算符返回整数。
符号差异
- 逻辑运算符用
||
、&&
。- 位运算符用单字符
|
、&
。
6、移位运算符
移位运算符是计算机编程中用于对二进制数进行位操作的运算符,主要分为左移运算符和右移运算符。它们直接操作数据的二进制表示,适用于性能敏感的底层开发(如嵌入式系统、图形处理等)。如果要进行移位操作, 则需要先获取操作数的二进制形式(补码) ,然后按位进行操作。
操作符 作用 例子 >>
算术右移位运算,也叫做带符号的右移运算 8 >> 1
<<
左移位运算 8 << 1
右移 >> 低位抛弃,高位补符号位的值#include < iostream >using namespace std ;int main (){// 0 0(23) 0000 1010int a = 10 ;cout << "a: " << a << endl ;// 0 0(23) 0000 1010// 00 0(23) 000 0101 [0] ==> 5int b = a >> 1 ;cout << "a >> 1: " << b << endl ;// 000 0(23) 0000 10 ==> 2b = 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// 结果: -3cout << "-10 >> 2: " << b << endl ;}左移 << 高位抛弃,低位补0#include < iostream >using namespace std ;int main (){int a = 10 ;cout << "a: " << a << endl ; // 10int b = a << 1 ;cout << "a << 1: " << b << endl ; // 20b = a << 2 ;cout << "a << 2: " << b << endl ; // 40b = 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 ==> -40cout << "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、优先级结合性
运算符优先级:决定了表达式中多个运算符的计算顺序。优先级高的运算符会先于优先级低的运算符执行。
结合性:当多个相同优先级的运算符连续出现时,结合性决定了它们的计算方向(从左到右或从右到左)。