trinity C Programming Slide 2

97 阅读11分钟

trinity C Programming Slide 1 重要知识点

1. Constant Suffixes (常量后缀)

  1. Understanding Constant Suffixes

英文:

In C, numeric constants can have suffixes that specify their type, which can affect how calculations are performed. Common suffixes include:

  • L or l: Indicates a long integer.
  • U or u: Indicates an unsigned integer.
  • F or f: Indicates a float.
  • D or d: Indicates a double.

中文:

在 C 语言中,数值常量可以具有后缀,以指定其类型,这会影响计算的执行方式。常见的后缀包括:

  • Ll:表示长整型。
  • Uu:表示无符号整型。
  • Ff:表示浮点数。
  • Dd:表示双精度浮点数。
  1. Example of Overflow Without Suffix

英文:

long int ans = 2000000000 * 2;
  • Explanation: In this line, 2000000000 is treated as an int by default because it has no suffix. The maximum value for a 32-bit signed integer is 2147483647. When you multiply 2000000000 by 2, the result (4000000000) exceeds the maximum value for a signed int, causing an overflow. This results in a negative value due to how integer overflow wraps around in binary representation.
  • Actual Result: The value of ans becomes -294967296, which is incorrect.

中文:

long int ans = 2000000000 * 2;
  • 解释:在这一行中,2000000000 默认被视为 int 类型,因为它没有后缀。32 位带符号整型的最大值为 2147483647。当你将 2000000000 乘以 2 时,结果(4000000000)超过了带符号 int 的最大值,导致 溢出。这会导致一个负值,因为整型溢出在二进制表示中是如何环绕的。
  • 实际结果ans 的值变为 -294967296,这是不正确的。
  1. Using the Long Suffix

英文:

long int ans = 2000000000L * 2;
  • Explanation: Here, the L suffix indicates that 2000000000 is a long integer. As a result, the multiplication operation is performed using the long type instead of the default int type. The maximum value for a 32-bit long is much larger than that of an int, so multiplying 2000000000L by 2 (which results in 4000000000) does not cause an overflow.
  • Actual Result: The value of ans is now correctly set to 4000000000.

中文:

long int ans = 2000000000L * 2;
  • 解释:在这里,L 后缀表示 2000000000 是一个长整型。因此,乘法操作是使用 long 类型而不是默认的 int 类型进行的。32 位 long 的最大值远大于 int 的最大值,因此将 2000000000L 乘以 2(结果为 4000000000)不会导致溢出。
  • 实际结果ans 的值现在正确地设置为 4000000000
  1. Key Takeaways

英文:

  • Constant Type Matters: When performing arithmetic operations, the types of constants involved determine the resulting type and can lead to overflow if not correctly specified.
  • Suffix Usage: Always use appropriate suffixes for constants to avoid unintended behavior, especially when dealing with large numbers or performing arithmetic that can exceed the limits of the default data types.
  • Preventing Overflows: By specifying the type of a constant, you can ensure that calculations are performed in a data type that can handle the expected range of values, thus avoiding overflow issues.

中文:

  • 常量类型重要:在执行算术操作时,涉及的常量类型决定了结果类型,如果未正确指定,可能会导致溢出。
  • 后缀使用:始终对常量使用适当的后缀,以避免意外行为,特别是在处理大数或执行可能超出默认数据类型限制的算术运算时。
  • 防止溢出:通过指定常量的类型,可以确保计算在能够处理预期值范围的数据类型中进行,从而避免溢出问题。
  1. Best Practices

英文:

  • When dealing with large constants or calculations, it’s a good practice to explicitly specify the type using suffixes.
  • Use the largest appropriate data type for calculations to avoid potential overflows. For instance, using long long int for very large numbers if necessary.

中文:

  • 在处理大常量或计算时,明确使用后缀指定类型是一个好习惯。
  • 对于计算,使用适当的最大数据类型以避免潜在的溢出。例如,在必要时对非常大的数字使用 long long int

通过遵循这些原则,您可以在 C 语言及类似语言中编写更安全、更可靠的代码。

2. Const

在 C/C++ 中,const 关键字用于定义不可修改的变量。它的使用可以增强代码的安全性和可读性。下面是 const 的各种用法及其详细说明:

2.1 声明常量 (Declaring Constants)

const 可以用来声明常量,表示该变量的值不能被修改。

const int MAX_VALUE = 100; // MAX_VALUE 是一个常量,值为 100
// MAX_VALUE is a constant with a value of 100.

2.2 Constant Pointers

const 可以与指针结合使用,指明指针指向的内容是常量,或者指针本身是常量。

  • 指向常量的指针: 不能通过该指针修改所指向的值。
const int* ptr = &MAX_VALUE; // ptr 指向一个常量整数
// *ptr = 200; // 错误:不能修改常量
// ptr points to a constant integer.
// Cannot modify the constant through ptr.
  • 常量指针: 指针本身是常量,不能更改指向的地址。
int value = 50;
int* const ptr = &value; // ptr 是一个常量指针
*ptr = 60; // 正确:可以修改值
// ptr = &anotherValue; // 错误:不能修改指针
// ptr is a constant pointer.
// Can modify the value, but cannot change the pointer itself.
  • 指向常量的常量指针: 既不能修改指针指向的内容,也不能更改指针本身。
const int* const ptr = &MAX_VALUE; // ptr 是指向常量的常量指针
// *ptr = 200; // 错误:不能修改常量
// ptr = &anotherValue; // 错误:不能修改指针
// ptr is a constant pointer to a constant integer.
// Cannot modify the constant or change the pointer.

2.3 函数参数 (Function Parameters)

在函数参数中使用 const 可以防止函数修改传入的参数,特别是对于引用和指针。

void printValue(const int* value) {
    // value 指向的内容不能被修改
    std::cout << *value << std::endl;
}
// The content pointed to by value cannot be modified.

以下是C++部分

2.4 常量成员函数 (Constant Member Functions)

在类中,常量成员函数是指不修改类的成员变量的函数。使用 const 修饰成员函数,表明该函数不会更改类的状态。

class MyClass {
public:
    void show() const { // show 是一个常量成员函数
        // this->value = 10; // 错误:不能修改成员变量
    }
};
// show is a constant member function.
// Cannot modify member variables within this function.

2.5 常量对象 (Constant Objects)

如果你想创建一个不允许修改的对象,可以将对象声明为 const

const MyClass obj; // obj 是一个常量对象
// obj.show(); // 正确,但无法修改 obj 的任何成员
// obj is a constant object.
// Can call show(), but cannot modify any members of obj.

2.6 常量表达式 (Constant Expressions)

C++11 引入了 constexpr 关键字,用于在编译时计算常量表达式,可以结合 const 使用。

constexpr int square(int x) {
    return x * x; // 返回 x 的平方
}
​
const int result = square(5); // result 在编译时计算
// square returns the square of x.
// result is computed at compile time.

2.5 总结 (Summary)

  • 常量声明: const 用于定义不可修改的变量。 Constant Declaration: const is used to define non-modifiable variables.
  • 常量指针: const 可与指针结合使用,指定指向的内容或指针本身是常量。 Constant Pointers: const can be combined with pointers to specify that the content pointed to or the pointer itself is constant.
  • 函数参数: 在函数参数中使用 const 防止修改。 Function Parameters: Use const in function parameters to prevent modification.
  • 常量成员函数: 通过 const 修饰成员函数,表示不修改类的状态。 Constant Member Functions: Use const to indicate that a member function does not modify the state of the class.
  • 常量对象: 可以声明不允许修改的对象。 Constant Objects: You can declare objects that cannot be modified.
  • 常量表达式: C++11 引入 constexpr,结合 const 使用。 Constant Expressions: C++11 introduced constexpr, which can be used with const.

使用 const 是一个良好的编程习惯,能够帮助你编写更安全、更高效的代码。 Using const is a good programming practice that helps you write safer and more efficient code.

3. C语言中的隐藏

#include <stdio.h>int main(void) {
    int b = 2;
​
    printf("b = %d\n", b); // 输出: b = 2
    {
        int b = 3;
        printf("b = %d\n", b); // 输出: b = 3
    }
​
    printf("b = %d\n", b); // 输出: b = 2
    return 0;
}
  • 第一次输出b:在 main 函数的作用域中定义一个整型变量 b,并将其初始化为 2。这个变量 bmain 函数的整个范围内可见。
  • 第二次输出b:创建了一个新的作用域(块作用域)。在这个块内部,定义了另一个同名的整型变量 b,并将其初始化为 3。这个新的 b 会隐藏外部作用域中的 b
  • 第三次输出b:结束了新创建的作用域。在这一点上,局部变量 b 不再可用,外部作用域中的 b 重新变得可见。

4. C语言中的语法糖

syntactic sugar AKA 语法糖

a+=b;   // a = a + b;
a-=b;   // a = a - b;
a*=b;   // a = a * b;
a/=10;  // a = a / b;
​
//同理
i++; // 叫后置++,因为++在后面
++i; // 叫前置++,因为++在前面
i--;
--i;
​

扩展:我们后面在学习C++的时候会知道++i 前置++的效率高一些

示例代码

以下是一个示例代码,演示了 ++ii++ 在自定义类中的区别:

#include <iostream>class Counter {
public:
    Counter() : count(0) {}
    
    // 前缀自增 ++i
    Counter& operator++() {
        ++count;
        return *this;
    }
    
    // 后缀自增 i++
    Counter operator++(int) {
        Counter temp = *this; // 创建副本
        ++count;
        return temp; // 返回副本
    }
​
    int getCount() const { return count; }
​
private:
    int count;
};
​
int main() {
    Counter c;
    
    ++c; // 前缀自增
    std::cout << "Count after ++c: " << c.getCount() << std::endl; // 输出: 1
    
    c++; // 后缀自增
    std::cout << "Count after c++: " << c.getCount() << std::endl; // 输出: 2
​
    return 0;
}

5. Bitwise operators (按位运算符)

a & b // AND
a | b // OR
a ^ b // XOR
~a // NOT
a << n // left shift
a >> n // right shift

Like arithmetic operators, there are in-place versions: e.g. a &= b;

a&&b  //AND. Returns true if both a and b are true (non-zero)
a||b  //OR. Returns true if either a or b are true (non-zero)
!a  // NOT. Returns true if a is false. Returns false if a is true

扩展问题1:a&ba&&b 有什么区别

&&& 是两种不同的操作符,它们在功能和用法上有显著的区别。

  1. 位运算符(&
  • 定义& 是位运算符(按位与运算符)。
  • 用法:用于按位对两个整数进行与运算。
  • 特点:对两个操作数的每一位进行比较,如果两个对应位都是 1,则结果位为 1,否则为 0

示例

#include <iostream>int main() {
    int a = 5;  // 二进制: 0101
    int b = 3;  // 二进制: 0011
​
    int result = a & b; // 0101 & 0011 = 0001 (十进制: 1)
    std::cout << "a & b = " << result << std::endl; // 输出: a & b = 1
​
    return 0;
}
  1. 逻辑运算符(&&
  • 定义&& 是逻辑与运算符(短路与)。
  • 用法:用于布尔逻辑运算,通常用于控制流(如条件语句)。
  • 特点:如果第一个操作数为 false,则不会计算第二个操作数(短路特性)。整个表达式的结果只有在两个操作数都为 true 时才为 true

示例

#include <iostream>bool conditionA() {
    std::cout << "Evaluating condition A" << std::endl;
    return true;
}
​
bool conditionB() {
    std::cout << "Evaluating condition B" << std::endl;
    return false;
}
​
int main() {
    if (conditionA() && conditionB()) {
        std::cout << "Both conditions are true." << std::endl;
    } else {
        std::cout << "At least one condition is false." << std::endl;
    }
​
    return 0;
}

输出

Evaluating condition A
Evaluating condition B
At least one condition is false.
  1. 总结
  • &(按位与)

    • 用于整数的按位操作。
    • 对每个二进制位进行比较。
  • &&(逻辑与)

    • 用于布尔逻辑操作。
    • 仅在两个条件都为 true 时返回 true,并具有短路特性。

使用场景

  • 使用 & 进行按位操作时,通常在处理位标志或执行位运算时使用。
  • 使用 && 进行逻辑判断时,通常在控制程序流时使用,如条件语句、循环等。

扩展问题2:如何交换a 和 b 两个数 : 引用,指针,bitwise

#include <iostream>// Swap using reference (C++)
void swapWithTemp(int& a, int& b) {
    int temp = a; // Use reference to get the value of a
    a = b;        // Assign the value of b to a
    b = temp;     // Assign temp (the original value of a) to b
}
​
// Swap using pointers 
void swapWithPointers(int* a, int* b) {
    int temp = *a; // Dereference the pointer to get the value of a
    *a = *b;       // Assign the value of b to a
    *b = temp;     // Assign temp (the original value of a) to b
}
​
// Swap using XOR 
void swapWithXOR(int* a, int* b) {
    if (a != b) { // Ensure that a and b point to different addresses
        // * has a higher precedence than ^ 优先级高
        *a = *a ^ *b; // Step 1
        *b = *a ^ *b; // Step 2
        *a = *a ^ *b; // Step 3
    }
}
​
// Another mathematical method that can easily cause overflow, not recommended// Main function
int main() {
    int a = 5;
    int b = 3;
​
    // Output initial values
    std::cout << "Before swap: a = " << a << ", b = " << b << std::endl;
​
    // Swap using a temporary variable
    swapWithTemp(a, b);
    std::cout << "After swap with temp: a = " << a << ", b = " << b << std::endl;
​
    // Reassign values
    a = 5; b = 3;
    // Swap using pointers
    swapWithPointers(&a, &b);
    std::cout << "After swap with pointers: a = " << a << ", b = " << b << std::endl;
​
    // Reassign values
    a = 5; b = 3;
    // Swap using XOR
    swapWithXOR(&a, &b);
    std::cout << "After swap with XOR: a = " << a << ", b = " << b << std::endl;
​
    return 0;
}
​