c++随笔-2

字符串

void CStyleStringType() {
    char str[] = "Hello";
    // 打印字符串长度
    std::cout << "str length: " << ::strlen(str) << std::endl;
    for (size_t i = 0; i < strlen(str); ++i) {
        std::cout << "index " << i << " value: " <<str[i] << std::endl;
    }

    char a[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
    std::cout << "a length: " << ::strlen(a) << std::endl;
    for (size_t i = 0; i < strlen(a); ++i) {
        std::cout << "index " << i << " value: " <<a[i] << std::endl;
    }

    char b[13]{};
    ::strcat(b, a);   // 字符串拼接
    for (size_t i = 0; i < strlen(b); ++i) {
        std::cout << "index " << i << " value: " <<b[i] << std::endl;
    }
    
    // 字符串内容比较 (str < a)-1,(str == a)0,(str > a)1
    std::cout << strcmp(str, a) << std::endl;
}

输出结果:
str length: 5
index 0 value: H
index 1 value: e
index 2 value: l
index 3 value: l
index 4 value: o
a length: 5
index 0 value: H
index 1 value: e
index 2 value: l
index 3 value: l
index 4 value: o
index 0 value: H
index 1 value: e
index 2 value: l
index 3 value: l
index 4 value: o
0

  • c风格字符串,以 char arr[6] = {‘H’, ‘e’, ‘l’, ‘l’, ‘o’, ‘\0’} 形式存储,注意末尾一定需要有’\0’
  • c提供头文件 #include 对字符串进行相关操作
  • c风格字符串中,‘\0’ 只是作为字符串的结尾,并不算在字符串长度中,但数组中必须有 ‘\0’ 的存储位置
void StingType() {
    std::string a = "Hello";   // 直接初始化
    std::cout << "a length: " << a.size() << std::endl;
    std::cout << "a value: " << a << std::endl;

    std::string b(a);   // 一个字符串初始化另一个字符串
    std::cout << "b value: " << b << std::endl;

    b = b + b;   // 拼接
    std::cout << "b + b value: " << b << std::endl;

    if (a == b) {
        std::cout << "a == b" << std::endl;
    } else {
        std::cout << "a != b" << std::endl;  // a 不等于 b
    }

    std::string c = "\\\\";  // 必须转义才能赋值 \ ;
    std::cout << "c value: " << c << std::endl;

    std::string d = R"(\\)";  // 原始字符,忽略转义
    std::cout << "d value: " << d << std::endl;

    // 必须带前缀才能初始化
    wchar_t e[] = L"Hello";   // 宽字符
    char16_t f[] = u"Hello";  // 对应 utf16 编码字符
    char32_t g[] = U"Hello";  // 对应 utf32 编码字符

    std::wcout << "e value: " << e << std::endl;

    // char16_t、char32_t 不能直接打印,需要转换
    std::cout << "f value: " << f << std::endl;
    std::cout << "g value: " << g << std::endl;
}

输出结果:
a length: 5
a value: Hello
b value: Hello
b + b value: HelloHello
a != b
c value: \
d value: \
e value: Hello
f value: 0x65fca8
g value: 0x65fc90

  • c++提供专门处理字符串的类 std::string 代替c风格的字符串处理,头文件为 #include < string >
  • 可以使用c风格字符串来初始化 string 对象
  • c++11可以使用初始化列表对其进行初始化
  • string 类重载了 =、+、==运算符,所以赋值、拼接、比较等操作更容易
  • string 还有多种的字符串字面值,c++11提供了 u8 和原始字符串 R 修饰字面值
  • 有关 string 类的操作函数有许多,也可通过STL的迭代器进行操作,先初步认识 string 类

结构体

void StructType() {
    struct Student {
        int id;
        std::string name;   // 非 POD 类型
    };

    struct Student a; // c
    Student b;        // c++
    a = {1, "a"};
    b.id = 2;
    b.name = "b";

    Student c = {3, "ccc"};
    Student d{4, "d"};

    struct Teacter {   // 默认初始化值
        int id = 0;    // 成员变量
        std::string name = "teacter";  // 非 POD 类型

        void Print() {  // 成员函数
            std::cout << "id:" << id << " name: " << name << std::endl;
        }

        Teacter(){}

        Teacter(int i, const std::string &n) {
            id = i;
            name = n;
        }
    };

    // 报错, 添加成员函数时,成员变量需要初始化,Teacter结构体没有两个参数的构造函数
    // int id = 0;
    // Teacter e = {20000, "Mr.Li"};
    Teacter f;  // 默认编译器生成无参构造函数
    f.id = 1000;
    f.name = "Mr.Li";
    f.Print();   // 调用成员函数

    // 添加 Teacter(int i, const std::string &n) {...}构造函数
    Teacter g{20000, "Mr.Li"};  // OK
    Teacter h = {3000, "Mr.Li"};  // OK
    // 因为 f 初始化时是编译器默认生成的无参构造函数,但添加有参构造函数时,
    // 编译时有构造函数编译器就不会自动生成无参构造函数,需要显式添加无参构造函数 Teacter(){},类似 f 这样的变量声明才能编译通过

    struct Bit {
         // int,4 * 8 = 32 bit
         int low : 10;
         int : 10;   // 占位,未使用
         int : 10;
    };
}

输出结果:
id:1000 name: Mr.Li

  • c++对c的结构体(struct)进行了功能拓展,基本上等同于类(class)的功能
  • 区别就是 struct 成员默认访问权限是 public ,class 成员默认访问权限是 private
  • 结构体之间可以嵌套,c++声明结构体类型变量时可以不加 struct 关键字
  • 初始化是按照声明的变量类型进行初始化(隐式转换亦可以),c++11的初始化列表({})也可以对结构体初始换
  • 使用初始化列表需要提供对应的构造函数
  • 结构体中出现这样直接给成员变量赋值的语句 int id = 0; 时,都需要提供对应的构造函数才能使用初始化列表
  • 结构体类型也可定义结构体数组变量,Teacher a[10];
  • 与c语言一样,c++也允许指定占用特定位数的结构体成员变量

共用体

void UnionType() {
    union IntConvert {
        int a;
        char arr[4];
    };
    IntConvert u;
    u.a = 21585461;
    std::cout <<"a value: " << u.a << std::endl;
    std::cout <<"arr[0] value: " << (int)u.arr[0] << std::endl;
    std::cout <<"arr[1] value: " << (int)u.arr[1] << std::endl;
    std::cout <<"arr[2] value: " << (int)u.arr[2] << std::endl;
    std::cout <<"arr[3] value: " << (int)u.arr[3] << std::endl;

    union TestUnion {
        short a;
        double b;
        int c;
    };
    std::cout <<"union length: " << sizeof (TestUnion) << std::endl;  // double 的长度
}

输出结果:
a value: 21585461
arr[0] value: 53
arr[1] value: 94
arr[2] value: 73
arr[3] value: 1
union length: 8

  • 存储不同的数据类型,但只能同时存储其中的一种类型,长度与存储类型中最大长度的类型长度一样
  • 匿名共用体即没有名称的共用体

枚举

void EnumType() {
	// 弱枚举类型
    enum Sq {
        E1,
        E2
    };
    std::cout << "E1 value: " << E1 << std::endl;   // 不使用作用域打印
    std::cout << "E2 value: " << E2 << std::endl;
    enum Sq2 {
        S1 = 10,
        S2 = 12
    };
    std::cout << "S1 value: " << Sq2::S1 << std::endl;  // 使用作用域打印
    std::cout << "S2 value: " << Sq2::S2 << std::endl;

    // 强类型枚举
    enum class Sq3 {
        N1 = 10,
        N2 = 12
    };
    // 打印时必须强制转换为 int 类型
    std::cout << "N1 value: " << (int)Sq3::N1 << std::endl;  // 必须使用作用域打印
    std::cout << "N2 value: " << (int)Sq3::N2 << std::endl;
}

输出结果:
E1 value: 0
E2 value: 1
S1 value: 10
S2 value: 12
N1 value: 10
N2 value: 12

  • c++提供了另一创建符号常量的方式,使用枚举方式,成员符号常量以逗号分隔
  • 枚举默认可以和整型相互转换,c++11提供了一种强类型枚举方式,不能和整数相互转换
  • 枚举默认第一个符号常量的整数值为 0,下一个在上一个数值的基础上默认加1
  • 枚举中符号常量对应的可以自指定,注意同一个枚举中的符号常量对应的整数不能有重复
  • 枚举也可以没有名字,即匿名枚举

指针

void PointerType() {
    // 声明指针
    int *p;    // 未初始化,指针指向内容未知
    char *p2;

    // 初始化
    int a = 10;
    int *p3 = &a;       // 指向变量 a 的地址
    int *p4 = nullptr;  // 初始化为 nullptr (未指向有效地址)
    int *p5 = p3;       // 指向 p3 所指向的地址

    // 对指针解引用
    std::cout << "p3 value: " << &p3 << std::endl;  // 输出p3自己的地址
    std::cout << "p3 value: " << p3 << std::endl;   // 输出p3指向的地址
    std::cout << "a value: " << &a << std::endl;    // 输出 a 的地址
    std::cout << "p3 value: " << *p3 << std::endl;  // 解引用,输出p3指向的地址的值

    // 通过指针修改指向变量的值
    *p3 = 12;
    std::cout << "a value: " << a << std::endl;    // 输出 a 的地址

    // 指针和数组
    int arr[10] = {1, 2, 3, 4, 5};
    for (int i = 0; i < 10; ++i)     // 输出数组
        std::cout << arr[i] << "\t";
    std::cout << std::endl;

    std::cout << "total array length: " << sizeof arr << std::endl;         // 数组总长度
    std::cout << "array size: " << sizeof arr / sizeof (int) << std::endl;  // 数组元素数量

    int *p6 = arr;                   // 数组名就是数组首地址
    for (int i = 0; i < 10; ++i)     // 通过指针操作输出数组
        std::cout << p6[i] << "\t";
    std::cout << std::endl;

    // 指针运算
    std::cout << "p6 address value: " << p6 << std::endl;
    for (int i = 0; i < 10; ++i)       // 通过指针操作输出数组
        std::cout << *(p6 + i) << "\t";// 注意p6指向的地址并未发生变化
    std::cout << std::endl;
    std::cout << "p6 address value: " << p6 << std::endl;

    for (int i = 0; i < 10; ++i)     // 通过指针操作输出数组
        std::cout << *p6++ << "\t";  // 注意p6指向的地址发生了变化,最终指向数组的最后一个元素的地址
    std::cout << std::endl;
    std::cout << "p6 address value: " << p6 << std::endl;
    std::cout << "arr last member var address value: " << &arr[10] << std::endl;

    std::cout << "p6 address value: " << --p6 << std::endl;
    std::cout << "arr last member var address value: " << &arr[9] << std::endl;

    // 静态联编 & 动态联编
    // 创建数组时,其大小就已经确定好了,采用静态联编(编译期间已经确定数组分配多少空间)
    // 使用指针创建数组时(动态数组),采用动态联编(运行时才动态分配空间)

    // 指针创建数组
    int *p7 = new int[10];             // 动态创建十个元素的 int 型数组,未初始化,随机值
    for (int i = 0; i < 10; ++i)       // 通过指针操作输出数组
        std::cout << *(p7 + i) << "\t";// 注意p6指向的地址并未发生变化
    std::cout << std::endl;
    int *p8 = new int[10]{0};          // 动态创建十个元素的 int 型数组,初始化为零
    for (int i = 0; i < 10; ++i)       // 通过指针操作输出数组
        std::cout << *(p8 + i) << "\t";// 注意p6指向的地址并未发生变化
    std::cout << std::endl;

    // c++需要手动管理程序内存等使用资源,处于栈空间的变量由系统自动释放,处于堆内存的空间需要手动管理
    // new & delete,new 关键字申请在堆内存中开辟空间,失败会抛出异常,所以需要手动释放,释放使用 delete 关键字
    int *p9 = new int[10]{0};
    delete [] p9;  // new type[] 形式申请的数组,必须匹配 delete [] 形式,否则出现内存泄漏
    p9 = nullptr;  // 指向不可用空间

    int *p10 = new int;
    delete p10;
    p10 = nullptr;
}

输出结果:
p3 value: 0x65fd28
p3 value: 0x65fd34
a value: 0x65fd34
p3 value: 10
a value: 12
1 2 3 4 5 0 0 0 0 0
total array length: 40
array size: 10
1 2 3 4 5 0 0 0 0 0
p6 address value: 0x65fd00
1 2 3 4 5 0 0 0 0 0
p6 address value: 0x65fd00
1 2 3 4 5 0 0 0 0 0
p6 address value: 0x65fd28
arr last member var address value: 0x65fd28
p6 address value: 0x65fd24
arr last member var address value: 0x65fd24
14352720 0 14352720 0 14352720 0 14352720 0 24 0
0 0 0 0 0 0 0 0 0 0

  • 指针是存储的是地址,而不是存储的值
  • new 关键字在堆上申请内存,需要手动管理,释放时使用 delete 关键字释放,注意匹配 new & delete,new [] & delete []

内存管理

int e = 10;         // 全局变量,全局/静态存储区
const int s = 10;   // 常量存储区

void NewDelete() {
    // 局部变量,自动存储区(栈)
    int a = 10;
    // 静态变量,全局/静态存储区
    static int b = 10;
    // 动态变量,动态存储区(自由存储区或堆)
    int *p = new int(10);
    delete p;
    p = nullptr;
    // 线程变量, 只在当前线程可用,线程存储区
    thread_local int t = 10;
}
  • 不同存储区对变量在程序中的生命周期有不同管理
  • 自动存储:函数内部定义的常规变量使用自动存储空间,自动创建,自动释放,无需手动管理,通常存储在栈(LIFO)中
  • 全局/静态存储:整个程序执行期间一直存在,static 关键字修饰的变量或定义在最外边的变量
  • 动态存储:使用 new 在堆上(或称为自由存储区)申请内存空间的变量,内存需要手动释放
  • 常量存储:存放的是常量,不允许修改
  • 线程存储:线程局部变量,c++11新添加的类型

循环语句

void ForCirculation() {               // for 循环
    char a[] = "HelloWorld";
    int size = strlen(a);

	// for循环执行顺序:1. i = 0; 2. 判断 i < size; 3. 执行循环体std::cout << a[i] << "\t"; 4. ++i; 
    // 5. 继续执行 2 步骤; 6. 继续执行 3 步骤; 7. 继续执行 4 步骤; ...
    for (int i = 0; i < size; ++i) {  // 标准 for 语法
        std::cout << a[i] << "\t";
    }
    std::cout << std::endl;

    int i = 0;
    for (; i < size; ++i) {           // 变体 1
        std::cout << a[i] << "\t";
    }
    std::cout << std::endl;

    int n = 0;
    for (; n < size;) {               // 变体 2
        std::cout << a[n] << "\t";
        ++n;
    }
    std::cout << std::endl;

    int s = 0;
    for (;;) {                       // 变体 3 死循环
        if (s == size)               // 满足条件跳出死循环
            break;
        std::cout << a[s++] << "\t";
    }
    std::cout << std::endl;
}

输出结果:
H e l l o W o r l d
H e l l o W o r l d
H e l l o W o r l d
H e l l o W o r l d

void WhileCirculation() {
    char a[] = "HelloWorld";
    int size = strlen(a);

    int i = 0;
    // 判断条件是 bool 值,未知循环次数,满足条件即可退出
    while (i < size) {       // 类似 for 循环
        std::cout << a[i++] << "\t";
    }
    std::cout << std::endl;

    i = 0;
    while (true) {           // 死循环
        std::cout << a[i] << "\t";
        if (a[i++] == 'd')   // 判断满足条件跳出死循环
            break;
    }
    std::cout << std::endl;
}

输出结果:
H e l l o W o r l d
H e l l o W o r l d

void DoWhileCirculation() {
    char a[] = "HelloWorld";
    int size = strlen(a);

    int i = 0;
    //  先执行循环体,在进行条件判断
    do {
        std::cout << a[i++] << "\t";
    } while (i < size);
    std::cout << std::endl;

    i = 0;
    do {
        std::cout << a[i++] << "\t";  // 改变i值,i = i + 1;
    } while (i == 0);                 // 不满足条件,不继续执行循环体,循环结束
    std::cout << std::endl;
}

输出结果:
H e l l o W o r l d
H

void CirculationControl() {
    char a[] = "HelloWorld";
    int size = strlen(a);

    for (int i = 0; i < size; ++i) {
        std::cout << a[i] << "\t";
        if (i == 4)   // 输出前五个字符
            break;    // 满足条件直接跳出循环
    }
    std::cout << std::endl;

    for (int i = 0; i < size; ++i) {
        if (i < 5)      // 输出除去前五个字符的内容
            continue;   // 满足条件跳到循环开始继续执行下一轮循环体,不在执行后面的循环体内容

        std::cout << a[i] << "\t";
    }
    std::cout << std::endl;

    for (int i = 0; i < size; ++i) {
        std::cout << a[i] << "\t";
        if (i == 4)    // 输出前五个字符
            return;    // 满足条件直接返回,整个函数执行结束
    }
}

输出结果:
H e l l o
W o r l d
H e l l o

  • 需要程序重复执行的任务可使用循环实现
  • 循环有三种方式,for循环、while循环和do while循环
  • 循环语句使用时注意不可出现死循环
  • 分支或循环控制关键字,break、continue和return

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值