赞
踩
目录
volatile、mutable和explicit关键字用法
X64 | X86 | |
---|---|---|
char | 1字节 | 1字节 |
short | 2字节 | 2字节 |
int | 4字节 | 4字节 |
long | 8字节 | 4字节 |
long long | 8字节 | 8字节 |
float | 4字节 | 4字节 |
double | 8字节 | 8字节 |
long double | 通常16字节 | 通常8字节 |
分为三类型:
1.char:标准类型
2.unsigned char:无符号char(0~255)
3.signed char:有符号char(-128~127)
字符串/字符数组
- char arr1[] = "hello"; 字符串:结尾有'\0'终止符,arr1占6字节
- char arr2[] = {'H', 'e', 'l', 'l', 'o'}; 字符数组:结尾无'\0',arr2占5字节
- '\0'影响printf等输出,输出结果为'\0'之前
字符串的申明方式
1.静态分配,编译时在栈上分配好内存
2.字符串常量
3.动态分配内存(malloc和new)
4.自动分配,在局部函数中
操作函数
- size_t strlen(const char *str) 返回字符串长度,不包含'\0'
- char *strcpy(char *dest, char *src) 把src复制给dest
- char *strncpy(char *dest, const char *src, size_t n) 把src复制给dest前n个字符
- char *strcat(char *dest, const char *src) 把src添加到dest的结尾
- char *strncat(char *dest, const char *src, size_t n) 把src前n个字符添加到dest结尾
- int strcmp(const char *str1, const char *str2) 比较字符串是否相同,成功返回0,失败返回不同个数
- int strncmp(const char *str1, const char *str2, size_t n) 比较前n个字符串,返回与strcmp相同
- char *strchr(const char *str, int c) 返回c第一次在str出现的地址
- char *strrchr(const char *str, int c) 返回c最后一次在str出现的地址
- char *strstr(const char *haystack, const char *needle) 返回needle在haystack第一次出现的地址
地址
- char arr[10] = "Hello";
- arr与&arr[0]所表示是一样的
- &arr是整个数组的地址,类型是char (*)[10],表示10个指向char元素的指针
指针数组与数组指针
- 指针数组,包含3个char的指针
- char *ptr_arry[3]
- 数组指针,指向一个包含3个char的数组
- char (*arry_ptr)[3]
初始化
- 1.没什么好说的
- int array[3][4];
- 2.编译器自动分配
- int array[][4] = {
- {1, 2, 3, 4},
- {5, 6, 7, 8},
- {9, 10, 11, 12}
- };
- 3.分配动态内存
- int rows = 3;
- int cols = 4;
- int **array = (int **)malloc(rows * sizeof(int *));
- for (int i = 0; i < rows; i++) {
- array[i] = (int *)malloc(cols * sizeof(int));
- }
- 4.使用stl容器
- int rows = 3;
- int cols = 4;
- std::vector<std::vector<int>> array(rows, std::vector<int>(cols));

地址
&a[1],&a[0]==a;一维数组地址
&a[0][1],&a[0][0] == a[0];元素地址
&a;二维数组地址
声明
- 1.默认声明
- struct Point {
- int x;
- int y;
- };
- C++的结构体class,为兼容c,保留了struct
- 2.定义并声明实例
- struct Point {
- int x;
- int y;
- } p1, p2;
- 3.结构体别名
- typedef struct {
- int x;
- int y;
- } Point;
- Point p1;
- C++默认声明,创建实例时不需要声明结构体struct或class
-

结构体大小
- struct Example {
- char a; // 1 byte
- int b; // 4 bytes
- char c; // 1 byte
- };
以上面为例,变量存储地址为自身所占字节大小的倍数,设从0x00开始,char占1个字节,地址在0x00,b占4字节,因为地址要是自身字节大小的整数倍,所以地址在0x04;c占1个字节,地址在0x08,所以Example结构体的大小为9字节。
其中,c特有的位域,变量+:+位数,用于修改变量大小,节省内存空间。计算方式为同类型变量合并,不足该变量字节大小的部分自动填充,不同类型的变量按结构体的计算方式存入地址。
链表
单链表:简单,节点只包含指向下一个节点的指针。
双向链表:支持双向遍历,节点包含指向前一个和下一个节点的指针。
循环链表:尾节点指向头节点,形成一个环,适合需要循环访问的情况。
C++中class与struct区别
(1)相同点
都能拥有成员函数,公有和私有部分;class可以实现的struct也可以实现。
(2)不同点
struct默认公有,class默认私有;struct默认公有继承,class默认私有继承。
(3)C++的struct与C的区别
C语言中struct是自定义数据类型;C++中是抽象数据类型,支持成员函数的定义(C++中可以继承和实现多态)
C语言中struct没有访问权限设置,成员只能是变量,但可以存入函数地址,数据不能被隐藏。
C++设置了访问权限,功能与class一样,默认是public访问。
C语言声明实例时必须在前面加struct,除非定义结构体时使用typedef。C++不需要,结构体struct在C++中被当作特例。
联合体声明
- union Data {
- int i;
- float f;
- char str[20];
- };
联合体大小计算
联合体的大小是其最大的数据成员大小的整数倍再满足编译器的内存对齐要求的最小倍数。以上面为例,联合体最大成员时char str[20],则联合体大小为20字节,若内存对齐要求为4字节,则不变;若为8字节,则填充4字节为24字节。
判断大小端
- #include <iostream>
- union data{
- int a;
- char b;
- }
-
- void main()
- {
- data udata;
- udata.a = 0x12345678;
- if(udata.b == 0x78)
- printf("Little-Endian\n");
- else if(udata.b == 0x12)
- printf("Big-Endian\n");
- else
- printf("Unkonwn Endian\n");
- }

- 分配内存,构造一个整数
- int* ptr = new int;
- 分配内存,构造函数参数进行构造
- MyClass* ptr = new MyClass(arg1, arg2);
- 分配数组内存
- MyClass* array = new MyClass[5];
- 或者memset设置内存初始值
- 使用std::nothrow,new失败时返回null
- type p = (type)malloc(分配大小)
- 分配失败时返回null
相同点
都可以动态申请内存
不同点
new是C++操作符,支持重载,还会调用构造函数;malloc是C/C++的标准函数。
new是类型安全的,malloc不安全。
new返回具体指针,malloc返回void型指针,需要类型转换。
new自动计算分配内存大小,malloc需要手动计算。
new是封装了malloc。
释放内存
- 释放单个对象的内存
- MyClass* obj = new MyClass(); // 用 new 分配对象
- delete obj; // 释放分配的对象内存
- 释放数组的内存
- MyClass* array = new MyClass[10]; // 用 new[] 分配对象数组
- delete[] array; // 释放分配的数组内存
- 一个new对应一个delete
- 释放内存后需要将指针指向空
- delete null是安全的
释放内存
- int *array = (int *)malloc(10 * sizeof(int));
- free(array);
- 一个malloc对应一个free
- 内存释放后将指针指向null,避免产生野指针
- free(NULL)会崩溃
new的实现过程:对简单的函数直接使用operator new函数;对复杂的数据结构调用operator new函数,分配足够大的原始为类型化的内存,运行该类型的构造函数并传入初始值,最后返回该对象的指针。
delete的实现过程:简单数据类型直接调用free;对复杂的数据结构对指针指向的对象运行析构函数,再用operator delete函数释放对象所使用内存。
new[]一个数组对象,需要知道数组的长度,会多分配4个字节,实际的数组所占内存为p-4;delete[]操作会取出这个数,知道要调用多少次析构函数。
这两个函数是由brk、mmp和munmap这些系统调用实现的。
brk是将堆顶的指针向高位移动,获得新的内存空间。mmap是在进程的虚拟地址空间(堆和栈中间,称为文件映射区)中找到一块空闲的内存块。这两种都是分配虚拟内存,没有分配实际的物理内存。在第一次访问已分配的虚拟地址空间,发生缺页中断(当程序访问的虚拟内存页面不存在物理内存时,会触发缺页中断),操作系统分配物理内存,建立虚拟内存与物理内存的映射关系。
malloc分配内存时,当分配内存小于128k,则使用brk在堆顶向高地址移动指针;当分配的内存大于128k时,使用mmap在虚拟地址空间寻找一块空闲内存。brk分配内存需要等高地址的内存释放后才能释放,而mmap分配的内存可以单独释放。当最高地址空间的空闲内存超过128K,则会执行内存紧缩。
不是的,被free回收的内存会用双链表ptmalloc保存,当下次申请内存的时候就尝试再内存中寻找合适的返回,避免反复的系统调用,同时tpmalloc也会尝试合并小块内存,防止产生过多内存碎片。
calloc省去了人为空间计算,calloc申请的空间的初始值是0;realloc给动态分配的空间分配额外的空间。
深拷贝和浅拷贝
浅拷贝
- class a{
- public:
- char *data;
- a(const char* str){
- data = new char[strlen(str)+1];
- strcpy(data, str);
- }
- a(const a &other) : data(other.data) {} //浅拷贝构造函数
- a();
- ~a();
- }
-
- int main()
- {
- a a1("hello world!");
- a a2 = a1;
- return 0;
- }

a1直接赋值给a2,实际a2使用的内存与a1使用的是同一块,a1内存被回收后,a2会访问无效内存,发生未定义行为。
深拷贝
- class a{
- public:
- char *data;
- a(const char* str){
- data = new char[strlen(str)+1];
- strcpy(data, str);
- }
- a(const a &other){
- data = new char[strlen(other.data)+1];
- strcpy(data, other.data);
- }//深拷贝构造函数
- a();
- ~a();
- }
-
- int main()
- {
- a a1("hello world!");
- a a2 = a1;
- return 0;
- }

a2不仅复制了a1的值,还分配了独立内存 。
数组指针:数组是指针;指针数组:成员是指针。
一级指针指向某数据类型的内存地址,二级指针指向一级指针的内存地址。
int (*funcPtr)(int, int);
它用于指向函数的内存地址。
32位一般是4字节,64位一般是8字节。地址+1是加了一个类型的大小。
(1)解引用运算符:*指针变量,表示地址操作符,取内容
(2)指针申明:表示指针变量
(3)运算:表示乘
- int b = 4;
- int &a = b;
是对已存在变量的别名。
const int &value
值不允许修改。
数组引用与指针一样。
- int *a = new int;
- int *(&b) = a;
不要引用局部变量。
(1)引用声明的初始化,指针不用马上初始化
(2)引用不能指向空,指针可以
(3)引用初始化后不能指向其他变量,指针可以
(4)引用效率高
(5)引用更安全,指针可以偏移
(6)指针更灵活,直接操作地址;指针更通用,C/C++都可以。
1.隐藏
2.保持变量内容的持久,即改变变量的生命周期。将static修饰的变量存入静态数据区(全局区)。
3.默认初始化为0。
4.在C++中类成员声明static:
(1)函数体内static变量的作用范围为该函数体,内存只分配一次。
(2)在模块内的static全局变量只能被该模块的函数访问。
(3)模块内的static函数只能被该模块的函数调用。
(4)类中的static成员变量属于类所有,对类的所有对象只有一份拷贝,该变量的初始化要在类外。
(5)类中的static成员函数属于类所有,这个函数没有this指针,只能访问类中的静态成员变量。
类内:
(6)static类对象要在类外初始化,因为static修饰的变量先类存在。
(7)static修饰的类成员属于类不属于对象,所以没有this指针,this指针是指向本成员的指针,所以无法访问非static的类成员。
(8)static成员函数不能被virtual修饰,static成员不属于任何对象和实例,virtual加上没有任何意义;静态成员没有this指针,虚函数的实现是对每个对象分配vptr指针,而vptr指针由this指针调用,所以不能为virtual。虚函数的调用关系:this->vptr->ctable->virtual function。
1.初始化只有一次,在主程序前编译器已经分配好内存。
2.静态局部变量与全局变量一样存在全局区。在C中,初始化发生在执行代码前,编译阶段分配内存后,就会初始化,所以C语言中无法用变量初始化静态局部变量;程序结束,变量所处的全局内存会被回收。
3.在C++中,初始化在执行相关代码时,主要时C++引入对象后,要进行初始化必须要用构造函数或析构函数,构造和析构函数一般需要执行相关的程序,而非简单分配内存。所以C++规定在首次使用到全局或静态变量时进行构造,并通过atexit()管理。在程序结束时,根据构造的顺序反方向析构,所以C++中的静态局部变量可以用变量初始化。
- int val = 10;
- 指针常量
- int* const a = &val;
- 常量指针
- const int *b = &val;
指针常量是指指针是常量,即指针指向的地址不可变,但指向地址的内容可变。
常量指针是指指针指向的内容不可变,地址可变。
定义变量的值是易变的,防止被编译器优化,声明后系统总是重新从内存中读取数据。
多线程下作用是防止优化编译器把内存装入CPU寄存器。
注意:可以把非volatile int赋给volatile int,但不能非volatile对象赋给volatile对象;用户定义的类型也可以用volatile修饰;volatile的类只能访问它的接口子集,一个由类控制者的子集,用户只能通过const_cast来获得对类型接口的完全访问。
volatile指针
- volatile指针
- volatile char *p;
- 指针volatile
- char* volatile p;
- 与常量指针和指针常量类似
volatile指针:指针指向的内容是易变的。
指针volatile:指针指向的地址是易变的。
修饰对象将永远可以修改。在const函数里修改与类状态无关的数据成员。
- class person{
- mutable int a;
- public:
- void add() const
- {
- a = 10;
- }
- };
-
- int main()
- {
- const person p = person();
- p.add();
- p.a = 40;
- return 0;
- }

explicit修饰构造函数,那么构造函数不能用隐式。explicit修饰转换操作符,必须显示转换。
- #include <iostream>
-
- class MyClass {
- public:
- explicit MyClass(int x) : value(x) {} // `explicit` 构造函数
- explicit operator int() const { return value; }
-
- int getValue() const { return value; }
-
- private:
- int value;
- };
-
- void printValue(const MyClass& obj) {
- std::cout << "Value: " << obj.getValue() << std::endl;
- }
-
- int main() {
- MyClass obj1(10); // 合法:显式调用构造函数
- printValue(obj1); // 合法:传递 `MyClass` 对象
- int x = static_cats<int>(obj) //合法: 显示数据类型转换
-
- // MyClass obj2 = 20; // 错误:隐式转换被禁用
-
- // printValue(30); // 错误:无法将 `int` 隐式转换为 `MyClass`
-
- return 0;
- }

指定该函数为父类的虚函数重写
- class A{
- virtual void foo();
- }
-
- class B: public A{
- virtual void fo0() override;//错误,函数一定是继承A的,找不到就报错
- virtual void f00(); //正确
- void foo(); //正确
- virtual void foo(); //正确
- void foo() override(); //正确
- }
不希望某个虚函数被继承或重写
- class Base
- {
- virtual void foo();
- }
- class A: public Base
- {
- void foo() final;
- }
- class B final : A
- {
- void foo() override; //错位,foo不能被重写
- }
- class C: B //错误,B不能被继承
- {
- }
定义:具有相同属性和行为的对象的集合。
- class A
- {
- public:
-
- void show()
- {
- cout<<a<<endl;
- }
- private:
- int a;
- protected:
-
- };
-
- struct B
- {
- public:
-
- private:
-
- protected:
-
- }

- person p;
- person *p = new person;
private:类内可见,class内不写访问修饰符,默认private;
public:类外可见,struct默认public;
protected:类内及子类可见;
- class A
- {
- private:
- int b;
- public:
- A() : b(5) {}
- friend void show(A &a);
- }
-
- void show(A &a)
- {
- cout<<a.b<<endl;
- }
-
- int main()
- {
- A a;
- show(a);
- return 0;
- }

- class A
- {
- private:
- int val;
- public:
- A() : val(20) {}
- friend class FriendA;
- }
-
- class FriendA
- {
- public:
- void show(A &a) const
- {
- cout<<a.val<<endl;
- }
- }
-
- int main()
- {
- A a;
- FriendA fa;
- fa.shwo(a);
- return 0;
- }

使用友元可以访问类的public,private和protected,继承可以访问public和protected。
特点
不受访问修饰符的影响;可以有多个友元;破坏了类的封装性,不是迫不得已,不要使用。
普通成员不能在类内直接赋值,因为只有对象的创建的时候才会分配空间,构造函数就是对数据成员赋值。
类名(参数列表){}
- class A
- {
- public:
- A() {}
- A(int b) {val = b;}
- A(int b, string str) {s = str;val = b;}
- private:
- string s;
- int val;
- }
声明栈区对象时若没有特指构造函数则会调用默认构造函数;声明堆区对象不会调用构造函数,是在new空间的时候调用。
(1)默认构造函数
什么都不做,即为空的。只要宏观声明的构造函数,默认的就没有了。
(2)无参数
(3)有参数构造函数
通过对象传递赋值;可以指定默认值;
(4)多个构造函数构成重载
- class A
- {
- public:
- A() : val(10) {}
- A(int b) : val(b) {}
- private:
- int val;
- };
后面的明天写
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。