C/C++笔记

数组指针&指针的数组&数组名

故事从int (*)[10]开始,这是一种数据类型,具体点是一种“指向‘长度为10的int数组’这种类型的指针”类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
int a[5] = {1,2,3,4,5};

int (*)[5] p1; //竟然是错误的用法。。。
int (*p2)[5] = a; //正不正确看编译器的种类和版本,不建议这么用
int (*p3)[5] = &a; //最正确的用法,注意,这里对a取地址得到的不是a的地址,而是将a的值直接赋过去,看起来就像没有取地址

/* 推荐用法 */
typedef uint8 Dt_ARRAY_700_uint8[700]; //先将数组定义成数据类型
Dt_ARRAY_700_uint8 uss_raw_data;

Dt_ARRAY_700_uint8 *ptr = &uss_raw_data; //正确用法
Dt_ARRAY_700_uint8 *ptr = uss_raw_data; //等价于上面p2那个不建议使用的用法,在qnx平台上编译时就报错了。

终结字符串、字符的数组、字符数组名

  1. 常规的char的数组
    1
    2
    3
    4
    5
    /* 基于C99标准 */
    char s1[5] = {'1', '2', '3', '4', '5'}; //常规的数组,只不过数组的元素是char
    char s2[ ] = {'1', '2', '3', '4', '5'}; //效果同s1,生成一个长度为5的数组
    char s3[6] = {'1', '2', '3', '4', '5'}; //s[6]未提供初始值,默认初始化为'\0'
    char s4[7] = {'1', '2', '3', '4', '5'}; //原理同s3,s[6]和s[7]都被初始化为'\0'
  2. 基于”char的数组”实现的字符串语法糖

    1
    2
    3
    4
    5
    /* 基于C99标准 */
    char s5[ ] = "12345"; //自动开辟6个Byte而不是5个,s[5]默认初始化为'\0',最终效果同s3
    char s6[6] = "12345"; //效果同s3和s5
    char s7[5] = "12345"; //效果同s1和s2
    char s8[7] = "12345"; //效果同s4
  3. 基于”char的指针”实现的字符串语法糖

    1
    2
    /* 基于C99标准 */
    char* s9 = "12345"; //自动开辟6个Byte而不是5个,s[5]默认初始化为'\0',最终效果同s3。但是,是在全局的只读数据区开辟,数组内容无法被修改。
  4. 总结
    当你想创建一个内存内容为{'1', '2', '3', '4', '5','\0'}的对象时,以下4种做法等价,正规来讲应该用s3,但最省事的肯定是s5和s9(所以说是两个语法糖)。然后再视是否需要修改对象内容来决定s5还是s9。

    1
    2
    3
    4
    char s3[6] = {'1', '2', '3', '4', '5'};
    char s5[ ] = "12345";
    char s6[6] = "12345";
    char* s9 = "12345";

    不过,在C99下发现s1,s2,s7这三个不以'\0'结尾的也可以用printf("%s", s)打印。。。不过,为了防止混淆,还是不要这么用。

cin的控制流转移逻辑

  1. 运行到cin >> x时,控制转移到终端,等待输入;
  2. 此时可以向cin流写入多个值(以空格或者tab切分),直到输入回车,从终端向cin流写入的过程停止,控制返回给程序。
  3. cin以分隔符将流内的内容喂给>>右侧的变量
  4. 再>>时,会先检查流是否为空,不为空则无需变更控制流,按照上一条的逻辑继续写目标变量。

未命名的命名空间与静态声明


静态声明的使用方法分两大类,其中面向过程的使用方法已经被C++标准取消,相应的符号隔离机制通过未命名的命名空间来实现。

define的特殊符号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#define Conn(x,y)  x##y
#define ToChar(x) #@x
#define ToString(x) #x

// ## 表示拼接
int n = Conn(123, 456);
int n = 123456;

// #@ 表示为后面的符号x增加单引号
char a = ToChar(1);
char a = '1';

// # 表示为后面的符号x增加双引号
char* s = ToString(123);
char* s = "123";

unique_ptr的两种指针释放方式

  1. release
    release只是简单的将被unique_ptr封装的内置指针吐出来,被指向的对象的内存不会被释放,内置指针与该对象之间的指向关系也不会被破坏。
  2. reset
    reset首先释放unique_ptr封装起来的那个内置指针所指向的内存,然后根据传入的参数对该内置指针进行赋值。

结构体排序

对于一串结构体,想排序,在C中可以基于stdlib库中的qsort接口配合一个自定义的比较函数实现。在C++中,有更elegant的做法。

首先,如果这个类重载了小于运算符,那么直接调用algorithm库中的sort函数即可进行升序排列;

其次,如果没有重载小于运算符,也可传入一个函数指针,或者就地定义一个lambda表达式。

最后说一下sort接口的语义:
在sort的过程,针对一对元素A和B,需要知道谁应该在前,谁应该在后。怎么判断呢?将A在前,B在后送入给到sort接口的函数(重载的小于运算符也好,函数指针也好,lambda表达式也好,都是),如果吐出来true,说明次序对,否则次序相反。

1
2
3
4
5
6
7
8
9
10
11
vector<Point> p = {····};

方式一:结构体重载了operator<
sort(v.begin(), v.end());

方式二:传入函数指针
bool compare(const Point& p1, const Point& p2) {return p1.x < p2.x;}
sort(v.begin(), v.end(), compare);

方式三:就地lambda表达式
sort(v.begin(), v.end(), [](const Point& p1, const Point& p2) {return p1.x < p2.x;});

另外还要提一点,就是STL中list容器的sort。algorithm提供的sort接口需要待排序结构体所在的容器提供随机访问迭代器,而list是不具备的。因此,list容器自己实现了sort成员函数。这个成员函数也是接受一个函数指针或者lambda表达式来check先后次序。

自定义数据结构如何使用范围for

1
2
3
MyClass mc;
for(auto ele : mc){
};

自定义的数据结构需要同时满足以下五个条件,就可以对其使用上述范围for语句对容器进行遍历:

  1. 定义了迭代器,指针也算;——用来迭代
  2. 定义了begin()end()方法;——用来启动和终止迭代
  3. 针对迭代器定义了!=运算符;——用来进行终止条件判定
  4. 针对迭代器定义了++前置自增运算符;——用来递增迭代器
  5. 针对迭代器定义了*解引用操作符;——用来访问容器元素

0做除数

无论整型还是浮点型,只要是0以字面值的形式做除数,那么编译都会报错;

1
2
3
//以下两种形式均无法通过编译
std::cout << 1/0 << std::endl;
std::cout << 1/0.0 << std::endl;

但如果是以变量的形式做除数,那么编译可通过,运行是否抛出异常则要看变量的类型;

  • 变量为整型,运行报错,division by zero
  • 变量为浮点型,运行不报错,只是除法运算的结果为inf
    1
    2
    3
    4
    5
    6
    7
    //编译不报错,运行报错
    int a = 0;
    std::cout << 1/a << std::endl;

    //编译不报错,运行也不报错
    double b = 0;
    std::cout << 1/b << std::endl;