C++学习注意点 agile Posted on Feb 26 2024 C++ 面试 - [vjhghjghj博客](https://blog.csdn.net/vjhghjghj/category_8698558.html) - [C++基础](https://blog.csdn.net/hou09tian/category_6849516.html) --- ### 静态成员变量需要注意的地方 --- - 必须`初始化`,且`必须在类外面初始化`,初始化时不能带有`static`关键字 - 如果类的声明`.h`和实现`.cpp`写在不同的文件中,即分离。那初始化必须在实现中,即`.cpp`中 - 静态成员变量跟对象没有关系,销毁对象不会影响静态成员变量 --- ###左值引用 --- ```C++ int i1 = 1; int &iRef = i1; // int &iRef2; //引用必须被初始化 int &iRef3 = iRef; // int &iRef4 = 10; //引用只能绑定在对象上,不能与字面值或者某个表达式的计算结果绑定在一起 ``` - 引用是为对象起了个另外一个名字。 ###指针 --- ```C++ void f(int i) { cout << "int" << endl; } void f(bool b) { cout << "bool" << endl; } void f(void *p) { cout << "point" << endl; } void TestPoint() { int i1 = 100; int *point1 = &i1; int **point2 = &point1; // point1:0x7ffeec35df2c,*point1:100, &point1:0x7ffeec35df20 // ,point2:0x7ffeec35df20,(*(&point1)):0x7ffeec35df2c // ,*point2:0x7ffeec35df2c,**point2:100 cout << "point1:" << point1 << ",*point1:" << *point1 << ", &point1:" << &point1 << ",point2:" << point2 << ",(*(&point1)):" << (*(&point1)) << ",*point2:" << *point2 << ",**point2:" << **point2 << endl; int *point3 = 0; int *point4 = NULL; // typedef decltype(nullptr) nullptr_t; int *point5 = nullptr; nullptr_t aNullptr; int *point6 = aNullptr; // *point6:0x0 cout << "*point6:" << point6 << endl; void *point7 = point1; //调用int方法 f(0); //通不过编译,有二义性 // f(NULL); //调用f(void*)方法 f(nullptr); } ``` --- - 相对于`0`或者`NULL`,优先选用`nullptr` - 避免在整形和指针类别之间重载 - 任何`非0指针`对应的条件值都是`true` --- ###复杂类型的声明 --- ```C++ //i2是一个int整形,p是一个int整形指针,r是一个int类型的引用 int i2 = 1024, *p = &i2, &r = i2; //p2是一个指向int的指针,p3是一个int int *p2, p3; //rp是一个对指针p的引用 //从右边向左阅读rp的定义,离变量名最近的符合(此时为&)对变量的类型有最直接的影响 //因此rp是一个引用,声明符的其他部分用以确定rp引用的类型是什么,rp是一个引用,他引用的是int指针 int *&rp = p; ``` --- - 面对一条比较复杂的指针或者引用的声明语句中,从右向左阅读有助于弄清楚他的真实含义 --- ###offsetof函数的使用和宏实现 --- ```C++ struct S { int a; char b; int c; char d; }; #define OFFSETOF(type, member) (size_t)&(((type*)0)->member) void testOffset() { // struct S size is 16 // a offsetof 0 // b offsetof 4 // c offsetof 8 // d offsetof 12 printf("struct S size is %d\n", sizeof(struct S)); printf("a offsetof %u\n", offsetof(struct S, a)); printf("b offsetof %u\n", offsetof(struct S, b)); printf("c offsetof %u\n", offsetof(struct S, c)); printf("d offsetof %u\n", offsetof(struct S, d)); // a OFFSETOF 0 // b OFFSETOF 4 // c OFFSETOF 8 // d OFFSETOF 12 printf("a OFFSETOF %u\n", OFFSETOF(struct S, a)); printf("b OFFSETOF %u\n", OFFSETOF(struct S, b)); printf("c OFFSETOF %u\n", OFFSETOF(struct S, c)); printf("d OFFSETOF %u\n", OFFSETOF(struct S, d)); } ``` --- - 返回结构体中一个成员在该结构体中的偏移量 --- ###ptrdiff_t - `ptrdiff_t`类型变量通常用来`保存两个指针减法`操作的结果 - ptrdiff_t通常被定义为`long int`类型,是`signed `整型 --- ```C++ char str[] = "hello,world!"; char *pstart = str; char *pend = str + strlen(str); ptrdiff_t ptrdiff = pend - pstart; // ptrdiff:12 cout << "ptrdiff:" << ptrdiff << endl; ``` --- ###intptr_t与uintptr_t类型 - `intptr_t`与`uintptr_t`类型用来存放指针地址 - `uintptr_t`是`intptr_t`的无符号版本 - 在64位机器上,`intptr_t` 为`long int`,`uintptr_t` 为`unsigned long int` - 非64位机器上,`intptr_t` 为 `int`,`uintptr_t` 为 `unsigned int` --- ####uintptr_t 和 void * 的区别 - `uintptr_t` 表示一个整数地址,在 C 和 C++ 中是`无符号整型数据类型`,`void *`是指向未知类型的指针类型 - `uintptr_t` 表示的是指针的地址,`void*` 表示的是指向某个内存地址的指针 --- ```C++ #define NAME_LEN 10 #define ID_LEN 12 typedef struct Student { char id[12]; char name[10]; uint8_t age; } stu; stu *create_Stu() { stu *st = (stu *) malloc(sizeof(stu)); return st == nullptr ? nullptr : st; } void free_Stu(stu *st) { if (st) { free(st); st = nullptr; } } static void init_Stu(stu *const st) { assert(st); const char *id = "200808477"; const char *name = "agile"; memcpy(st->name, name, strlen(name)); memcpy(st->id, id, strlen(id)); st->age = 18; } void testPtr() { stu *st = create_Stu(); init_Stu(st); auto intptr = (intptr_t) st; auto uintptr = (uintptr_t) st; // intptr:140724348666064,uintptr:140724348666064 cout << "intptr:" << intptr << ",uintptr:" << uintptr << endl; // sizeof(stu):23 // OFFSETOF(stu, id):0 // OFFSETOF(stu, name):12 // OFFSETOF(stu, age):22 cout << "sizeof(stu):" << sizeof(stu) << endl; cout << "OFFSETOF(stu, id):" << OFFSETOF(stu, id) << endl; cout << "OFFSETOF(stu, name):" << OFFSETOF(stu, name) << endl; cout << "OFFSETOF(stu, age):" << OFFSETOF(stu, age) << endl; free_Stu(st); } ``` --- ###XOR 链表 --- ```C++ //aa ^= bb; //bb ^= aa; //此时bb=aa //aa ^= bb; //此时aa=bb struct item { uintptr_t link; int data; }; struct xor_list { struct item *head; struct item *tail; }; struct item *get_link(struct item *ip, const struct item *previous) { return (struct item *) (ip->link ^ (uintptr_t) previous); } struct item *get_next(struct item *ip, struct item **previous) { struct item *next = get_link(ip, *previous); *previous = ip; return next; } uintptr_t make_link(struct item *prev, const struct item *next) { return (uintptr_t) prev ^ (uintptr_t) next; } struct item *add_item(struct xor_list *lp, int data) { struct item *ip = (item *) malloc(sizeof(*ip)); if (ip) { struct item *tail = lp->tail; ip->data = data; if (tail) { struct item *prev = get_link(lp->tail, NULL); ip->link = make_link(tail, NULL); tail->link = make_link(prev, ip); lp->tail = ip; } else { ip->link = make_link(NULL, NULL); lp->head = lp->tail = ip; } } return ip; } void TestXor() { struct xor_list list = {NULL, NULL}; struct item *ip, *prev; add_item(&list, 1); add_item(&list, 2); add_item(&list, 3); add_item(&list, 4); add_item(&list, 5); printf("traversing from head to tail:"); for (prev = NULL, ip = list.head; ip; ip = get_next(ip, &prev)) { printf(" %d", ip->data); } printf("\n"); printf("traversing from tail to head:"); for (prev = NULL, ip = list.tail; ip; ip = get_next(ip, &prev)) { printf(" %d", ip->data); } printf("\n"); } ``` --- ###动态内存函数malloc,calloc,realloc详解 --- ![706a046dcb2848f790dc1750ba595860.png](https://tools.nxcloud.club:12500/images/2024/02/29/706a046dcb2848f790dc1750ba595860.png) --- - 使用开辟空间得到的地址时先判断一下是否为空指针再使用(`避免对NULL进行使用`) - 使用时不要改变p指针的指向.(`避免free时出现错误`) - 使用完成以后要对动态内存空间进行释放以及让释放以后的指针指向NULL.(`避免出现内存泄漏和野指针`) - 不要对动态内存进行多次释放。也不要对不是动态内存的空间进行释放。(`避免free一段已释放的空间以及对不是动态内存的空间进行释放`) --- ###C++中delete和delete[]的区别 - `基本类型`组成的数组空间用`delete`和`delete[]` 都是应该可以 - `类对象数组`,只能用 `delete[]`, new 的单个对象,只能用`delete`不能用`delete[]`回收空间 - 一个简单的使用原则就是:`new 和 delete、new[] 和 delete[] 对应使用` --- ###内存对齐(64位mac) - `指针point`:8 字节 - `枚举 enum`:4 字节 - `联合体 union`:取`union`中最大一个变量类型大小 ####对齐规则: - 结构体嵌套:子结构体的成员变量起始地址要视`子结构体中最大变量类型决定`,比如 struct a 含有 struct b,b 里有 char,int,double 等元素,那 b 应该从 8 的整数倍开始存储 - 含数组成员:比如 `char a[5]`,它的对齐方式和连续写 5 个`char` 类型变量是一样的,也就是说它还是按一个字节对齐 - 含联合体(union)成员:取联合体中`最大类型`的整数倍地址开始存储 - `#pragma pack (n)`:让变量强制按照 n 的倍数进行对齐,并会影响到结构体结尾地址的补齐 --- ```C++ class Child { char a[18]; double b; char c; int d; short e; }; //sizeof(Child):48 //(8+8+8)+8+((1+3)+4)+(2+6)=48 cout << "sizeof(Child):" << sizeof(Child) << endl; class Child { char a[15]; int b[3]; short c; char d; int e; short f; }; //sizeof(Child):40 //(4+4+4+4)+(4+4+4)+(2+1+1)+4+(2+2) = 40 cout << "sizeof(Child):" << sizeof(Child) << endl; class Child { int a; char b[8]; float c; short d; }; //sizeof(Child):40 //4+(4+4)+4+(2+2)=20 cout << "sizeof(Child):" << sizeof(Child) << endl; enum DAY { MON = 1, TUE, WED, THU, FRI, SAT, SUN }; class Child { char a[5]; char b[3]; enum DAY day; int *c; short d; int e; }; //sizeof(Child):40 //(5+3)+(4+4)+8+(2+2+4) = 40 cout << "sizeof(Child):" << sizeof(Child) << endl; struct stu2 { char x; int y; double z; char v[6]; }; class Child { union u1 { int a1; char a2[5]; } a; struct stu2 b; int c; }; //sizeof(Child):40 //(5+3)+((1+3+4)+8+(6+2))+(4+4) = 40 cout << "sizeof(Child):" << sizeof(Child) << endl; class Child { char a; struct stu2 b; int c; }; //sizeof(Child):40 //(1+7)+((1+3+4)+8+(6+2))+(4+4) = 40 cout << "sizeof(Child):" << sizeof(Child) << endl; #pragma pack(2)// 强制以 2 的倍数进行对齐 class Child { short a; int b; long c; char d; }; #pragma pack()// 取消强制对齐,恢复系统默认对齐 //sizeof(Child):16 //2+4+8+(1+1)=16 cout << "sizeof(Child):" << sizeof(Child) << endl; ``` --- ###前向声明 - 可以声明一个类而不定义它。例如:`class name`。在声明之后,定义之前,类`name`是一个不完全类型(incompete type),即已知`name`是一个类型,但不知道包含哪些成员 - 不完全类型只能用于定义指向`该类型的指针`及`引用` - 用于声明(而不是定义)使用该类型作为形参类型或返回类型的函数 ```C++ #include "A.h" class B { A a; }; class B; class A { B *b; void Test(B b); B TestRB(); }; ``` --- ###使用 memset 函数初始化时需要注意的地方 - memset 函数声明为`void *memset(void *str, int c, size_t n) ` - 功能是将 str 中当前位置后面的 n 个字节 (typedef unsigned int size_t )用 c 替换并返回 str - `memset`函数是按`字节`对内存块进行初始化的 - `memset`函数赋值推荐用`-1`,`0`,`0x3f`,`0xff` - `memset`如果用`1`赋值,因为`memset`是每个字节进行赋值的,一个`int`为4字节,那么`memset`赋值时为二进制则为`00000001 00000001 00000001 00000001 = 16843009` - `-1`的补码是`11111111`,所以`11111111 11111111 11111111 11111111`也是`-1` - `0xff`转为二进制位`11111111`,正好是一位赋值的初始化就是`-1` - `memset(a,0x3f,sizeof(a))`是为了给这段内存置为无穷大,因为`0x3f3f3f3f`的十进制是`1061109567`,也就是`10^9`级别的(和`0x7fffffff`一个数量级),而一般场合下的数据都是小于`10^9`的,所以它可以作为无穷大使用而不致出现数据大于无穷大的情形。同时他满足了“无穷大加一个有穷的数依然是无穷大”。`0x3f3f3f3f+0x3f3f3f3f=2122219134`这非常大但却没有超过32-bit int的表示范围 --- - 排序 url