C++基础知识粗略整理

C++基础知识粗略整理

static关键字

  • 声明静态全局/局部变量时,编译时初始化,与对象不同,存储于全局/静态区
  • 声明全局静态对象,存储在全局/静态区,首次调用时初始化,作用于整个程序,程序结束是销毁
  • 声明局部静态对象,存储于全局/静态区,函数内部声明,首次调用时初始化,一直存于内存,仅作用于该函数,程序结束是销毁
  • 静态类数据成员,归属于类,类对象共享,类外初始化,类对象均可以访问
  • 声明静态函数仅本文件可见(仅在本文件内可调用,默认extern)
  • 静态类函数成员,归属于类,只能访问static静态类数据成员

const关键字

  • 声明变量为常数变量,限定只读,存储于常量区
  • const T* p; 不可通过p指针修改对象值;T* const p; 常量指针,指针不可被赋值,地址不可改变(1. 地址可变,值不变2. 地址不变,值可变)
  • const T function(const T, const T*, T* const, const T&) const &/&& {…;} 返回值常量;常量形参;指针常量;常量指针;常引用;防止对象属性被改变
  • 突破const的限制,mutable在const函数中修改成员变量
  • const修饰的成员函数不能修改对象内的成员变量,且不能调用非const成员函数
  • const修饰类对象,该对象只能访问const修饰的函数

extern关键字

  • 置于变量或者函数前,表明变量或者函数在别的文件中定义的,提示编译器到别的模块中找
  • extern “C”,告诉链接器链接的时候用C函数规范来链接,因为C++支持函数重载,C不支持,所以编译后的函数在库中名称不同

volatile关键字

  • 修饰变量,告诉编译器该变量随时可能改变,不能放到寄存器中
  • 防止脏读,增加内存屏障
  • 易变性:在汇编层面,两条语句,下一条语句不会直接使用上一条语句对应的volatile变量的寄存器内容,而是从内存中读取
  • 不可优化性:告诉编译器,不要对修饰变量进行各种激进的优化,甚至将变量删除,保证程序员写在代码中的指令,一定会被执行
  • 顺序性:能够保证volatile变量间的顺序性,编译器不会进行乱序优化

register关键字

  • 修饰变量,请求编译器将变量存储于CPU内部的寄存器中,而不是通过内存寻址,提高效率
  • 仅是建议编译器这么做,不是必须

assert()

  • 断言,是宏定义,不是函数
  • 定义在<assert.h>(C)、<assert>(C++)中,在条件返回false是终止程序
  • 可以试用#define NDEBUG关闭assert,但是需要置于头文件前

sizeof()

  • 对数组,返回整个数组所占空间大小
  • 对指针,返回指针本身所占空间大小

#pragma pack(n)

  • 设定结构体、联合以及类成员变量以n字节方式对齐

struct和typedef struct

  • C中
  • typedef struct Student{} S;和typedef struct Student S;
  • S和struct Student等价,但两个标识符名称空间不同
  • 还可以定义与之不冲突的void Student(){}
  • C++中
  • 使用Student me;时,编译器先在全局标识符表查找,再从类标识符内搜索
  • 如果定义了同名函数,则Student只代表函数,不代表结构体

union联合

  • 节省空间的特殊的类
  • 可以包含多个数据成员,但是任意时刻只有一个成员合一有值
  • 当某个成员被赋值后,其他成员变为未定义状态
  • 默认为public
  • 可以包含构造函数和析构函数
  • 不能含有引用类型成员
  • 不能继承其他类,不能作为基类
  • 不能含有虚函数
  • 匿名union在定义作用域可直接访问union成员
  • 匿名union不能包含protected成员或private成员
  • 全局匿名联合必须是静态的

C/C++区别

  • C面向过程语言,编译型(用struct可以实现对象化,没有C++强大,且成员为public)
  • C++面向对象语言、编译型、拥有封装、继承、多态三大特性,支持泛型编程(模板)

C++面向对象

  • 封装:将客观事物封装到抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏;防止代码被无意破坏
  • 继承:基类(父类)---->派生类(子类);代码重用,节省开发时间
  • 多态:多种状态,一种接口多种方法
  • C++多态分类及实现:
  1. 重载多态:函数重载、运算符重载
  2. 子类型多态:虚函数
  3. 参数多态:类模板、函数模板
  4. 强制多态:基本类型转换、自定义类型转换

继承和多态的区别和联系

  • 继承是子类获得父类的成员函数,多态时父类可以调用子类的方法
  • 继承中父类更通用,子类更具体
  • 多态需要通过父类指针调用子类重写父类中的虚函数

虚函数与纯虚函数的区别

  • 二者可定义再同一个类中,含有纯虚函数的类为抽象类,不可实例化,只含有虚函数的类不能称为抽象类
  • 虚函数可以直接使用,也可在子类中重载以多态形式调用,纯虚函数必须在子类中实现函数才可以使用,纯虚函数在基类中只有声明没有定义
  • 二者均可在子类中重载,多态的形式调用
  • 为了能够方便的使用多态特性,常常需要在基类中设置虚拟函数
  • 在多数情况下,基类本身生成对象不合常理,所以引入了纯虚函数

虚函数表

  • 编译器为每个类维护一张虚函数表
  • 每个对象首地址存放虚函数表指针,同一个类的不同对象使用同一个虚函数表
  • 纯虚函数相当于占位符,在派生类中实现后把真正的指针地址填入
  • 子类完全获得父类的虚函数表,重写过则更新相应函数地址

构造函数、析构函数是否可以是虚函数

  • 构造函数不能是虚函数,虚函数需要通过虚函数表调用,但是虚函数表在构造函数中初始化,产生了矛盾
  • 析构函数可以是虚函数,且大部分情况下必须是虚函数,在使用基类指针指向派生类对象时,进行析构的时候,如果基类析构函数不是虚函数的话,只会调用基类的析构函数,不调用派生类析构函数,从而造成内存泄漏(可以不是虚函数,但是要保证不发生多态绑定)

静态函数与虚函数,虚析构

  • 静态函数编译时绑定,虚函数动态绑定
  • 静态多态:函数重载;动态多态:虚函数
  • 静态函数不能是虚函数
  • 虚函数有virtual标识,派生类覆盖基类虚函数,基类指针指向派生类对象时,实现动态多态,运行时判断指针指向的对象类型,从而调用相应的函数

拷贝构造函数必须为引用传递,不能是值传递

  • 首先明确拷贝构造函数是用于对象复制的,将一个实例用来初始化另一个实例
  • 然后要知道参数传递的过程中发生了什么,无论是引用传递(地址传递)还是值传递,都是传递了一个“值”。引用传递的话,只是将一个地址传个了形参,形参和实参指向的是同一个地址;值传递的话,如果形参是基础数据类型(int,char之类),也是普通的拷贝给形参,但是,如果形参是自定义类型(类)则需要先创建一个临时对象,然后调用该对象的拷贝函数,呐!!!出现了,值传递的拷贝函数无限调用

内联函数、宏定义和普通函数

  • 内联函数要做参数类型检查,这是内联函数和宏定义的区别以及优势
  • 宏定义是在预编译阶段把宏名替换成宏体的,属于字符串的替换,内联函数则是在编译阶段进行代码插入,这样可以省去函数调用时的压栈和出栈,提高效率
  • inline只是建议编译器进行内联函数操作,并不是必须的,主要还是看编译器怎么编
  • 一般编译器不内联包含循环、地柜、switch等复杂操作的函数
  • 在类中定义的函数(除虚函数外),自动转为内联函数,可以访问成员变量,宏定义不行
  • 内敛函数在运行时可以调试,宏定义不行
  • inline函数无法随着函数库升级而升级。需要重新编译(其他函数可以直接链接)

const和#define优缺点

  • 二者均可以用于定义常量
  • const进行类型检查, #define是字符串替换
  • #define会消耗编译时间,但是缩短了运行时间

虚函数和内联函数

  • 虚函数可以是内联函数,但是在表示多态时不行
  • 内联函数是在编译时内联,但是虚函数表现多态时是在运行时确定调用的函数
  • 编译器需要知道实际的对象才能内联

new和malloc区别

  • 二者均是用于动态申请内存空间
  • malloc和free是C/C++中的标准库的函数,new和delete是C++的运算符
  • malloc和free仅用于内部数据类型的内存申请,因为对象需要执行构造函数和析构函数,malloc与free无法满足
  • new可以理解为malloc+构造函数一起执行,并且返回的指针自带相应的数据类型,malloc返回的是void指针
  • 属性:new/delete是关键字;malloc/free是库函数
  • 参数:new申请内存时无须指定大小,编译器自行计算;malloc参数中需要给出内存的尺寸
  • 返回类型:new分配成功时返回是对象类型的指针,无须再进行类型转换;malloc反馈void指针,需要类型转换
  • 分配失败:new失败是抛出bad_alloc异常;malloc失败时返回NULL
  • 自定义类型:new先调用operator new函数申请内存,然后调用构造函数初始化成员变量,delete先调用析构函数,然后调用operator函数释放内存;malloc/free函数只能动态申请和释放内存,无法强制要求其做自定义类型对象的构造和析构,一般用于基本类型的申请和释放
  • 重载:new/delete是操作符,可以重载;malloc/free是库函数,不允许重载

浅复制和深复制

  • 复制内容为值的话,二者无区别
  • 复制内容存在引用的话,浅复制仅复制引用,复制前后的引用指向同一个地址,深复制的话,会将引用的地址内容也复制一份,引用改为新复制的地址

指针、引用指针区别

  • 指针存放对象的地址,引用指针为对象地址的别名,指向同一个地址,均可修改对象的值
  • 指针需要使用*解引用,而引用不需要
  • 指针可以被重新赋值,引用指针初始化后不可修改
  • 指针可以先定义再赋值,引用指针定义时需初始化(严谨一点是在分配内存时需要初始化)
  • 二者均占内存
  • 传指针会发生拷贝,引用指针不发生拷贝,提高代码效率
  • 指针可以指向指针,不能建立引用的引用,不能建立引用的指针
  • 指针可以const,引用不行
  • 二者的++运算意义不同

迭代器和普通指针

  • 迭代器不是指针,是类模板,表现的像指针,通过重载了指针的一些操作符->、*、++、–等封装了指针,是一个可以遍历STL容器元素的对象,本质是封装了原生指针
  • 迭代器反馈的是对象引用,而不是对象的值,所以使用cout输出的时候需要*解引用
  • 指针可以指向函数,迭代器不行
  • 指正是迭代器的一种,指针只能用于某些特定的容器,迭代器是指针的抽象和泛化

智能指针

  • 动态申请内存空间后,未进行释放,导致内存泄漏,可考虑使用智能指针
  • auto_ptr,用类来存储指针,保存于栈中,离开作用域调用析构函数,确保内存释放,但是多个auto_ptr初始化同一个对象默认复制构造函数,执行浅复制,均指向同一个内存,析构时会多次释放同一空间,程序崩溃;对STL不兼容,STL中对象复制是进行复制,不是移动语义;其内容是非数组delete,动态申请的数组无法正常释放,目前已弃用
  • unique_ptr ptr[左值](class var[右值]) 对象可以传只给左值常量引用函数,不会改变内存所有权,也可以将右值传值,实现移动语义;同一个资源不要初始化多个unique_ptr对象,不要混用普通指针和智能指针
  • shared_ptr与上一个区别在于该指针使用引用计数,意味着统一资源可被多个shared_ptr指向,在最后一个离开作用域时释放内存。不能用于管理C语言风格的动态数组,在一个shared_ptr对象中引用另一个shared_ptr对象会引起死锁,从而导致内存泄漏
  • weak_ptr弱引用,可以包含shared_ptr管理的内存引用,但不拥有,不会阻止shared_ptr释放内存,可通过lock()返回一个shared_ptr对象

数组、指针和野指针

  • 内置数组是一块连续的内存空间,数组名是一个指针,指向数组第一个元素地址
  • 野指针指向已删除的对象地址或其他未申请地址的指针,访问野指针很危险
  • 定义指针未初始化,指针内容被释放后未赋值NULL,指针操作超越变量作用域
  • 防止数组越界,如果数组越界了编译不会通过
  • 防止想一块内存中拷贝过多的内容
  • 防止使用空指针
  • 防止修改const修饰的指针
  • 防止修改指向静态存储区的内容
  • 防止使用野指针
  • 防止两个指针释放同一个内存
  • 堆上的指针,可以保存在全局数据结构中,供不同函数使用访问同一块内存
  • 栈上的指针,在函数退出后,该内存即不可访问

指针、地址、数组名

  • 指针是一个变量,它存储的值是一个地址,地址是内存的一个固定的点,指针通常没有限制的话可以指向任意合法地址
  • 指针和数组名都是指向一个地址,指针可以变化,数组名不可以

函数指针

  • 指向函数的指针,将函数地址赋值给指针
  • int (*fp)(int, int); 定义方式
  • fp=&max; 赋值
  • fp(1,2); 直接调用
  • int cal(int a, int b, int (*fp)(int, int)); 参数传递

函数重载二义性

  • 编译阶段进行函数匹配时出现的错误
  • 默认参数与无参函数
  • 隐式类型转换造成,类类型转换造成
  • 解决办法:编程时使用explicit关键字声明函数

重载、覆盖和隐藏

  • 程序在编译时绑定函数为静态特性(重载),带运行时绑定函数为动态特性(覆盖,重写,虚函数实现)
  • 重载:相同函数名,不同参数个数/类型的函数,给定不同参数实现静态多态
  • 覆盖:多用在类继承中,派生类继承基类后,可实现自己的函数版本,虚函数在后面加上关键字override
  • 隐藏:派生类实现了与基类同名函数,导致基类函数在派生类中不可见,调用隐藏函数时,取决于指针类型,而不是对象类型

STL map与set区别于实现

  • 均为关联容器
  • map为映射,以map<type,type>(key,value);键值对的形式存储
  • set只存储key,且元素不重复,插入时,调用红黑树的insert_unique函数,防止重复
  • map可通过下标访问,set不可以,map下标查找不到时,会插入一条

STL 迭代器与指针的区别,迭代器如何删除元素

  • 迭代器为STL的关键所在
  • STL中心思想是把容器和算法分离开发,独立设计成泛型(类模板和函数模板),在用一种粘合剂将二者结合起来,迭代器就是该角色
  • 迭代器按照容器内部次序访问元素,避免容器结构暴露
  • map/set族使用红黑树实现;unorderd族使用hashtable;vector使用内置数组,通过三个核心指针实现;queue和list使用双端队列deque实现,使用指针的话就需要了解不同容器的内部实现,使用迭代器可直接map.begin()/vec.end()进行访问

vector和list区别

  • vector是一个动态数组,可以扩容,当超过当前容量时,扩容两倍,不够就扩大到需要的大小
  • 重新分配空间,数据拷贝,新数据初始化
  • 连续内存地址,随机访问,删除插入O(n)
  • 相当于数组和链表的区别,但可以扩容

类成员访问权限

  • private成员仅在类内访问,类外不可见,派生类不可见,对象不可访问
  • protected成员类内访问,派生类可访问,对象不可访问
  • public成员类内、派生类、对象均可访问

struct和class

  • struct从C继承过来,可实现class的功能,但无法实现访问权限控制,默认为public
  • calss可以用于声明模板,struct不可以

C++内存管理/分配

在这里插入图片描述

  • 栈区
    一些局部分量,由编译器管理,一般几M,栈向低地址扩展,有系统提供,有专门的寄存器和指令负责
  • 堆区
    动态申请的内存,new出来的地址,由程序员自己申请和释放,一般几G,堆向高地址扩展,由C++程序实现,复杂的算法,效率低于栈,会产生碎片内存
  • 全局/静态区
    全局变量、static静态变量
  • 常量区
    存储字符串等常量,const修饰变量
  • 代码区
    存储函数内容
  • BSS段
    用于存储程序中未初始化或者初始化为0的全局变量或者静态变量

如何限制一个类对象只能在堆(栈)上分配空间

  • C++中对象建立两种方式:静态建立A a; 动态建立 A *ptr=new A;
  • 静态建立的对象存储于栈中,编译器通过栈顶指针移出适当空间,然后再这个空间中调用构造函数形成对象
  • 动态建立的对象存储于堆中,先执行new申请空间,然后调用构造函数初始化空间
  • 只能建立在堆上
  • 将构造函数和析构函数权限设为protected,提供一个public的static函数来实现构造和析构
class A {
protected:
	A(){}
	~A(){}
public:
	static A* create() {
		return new A();
	}
	void destroy() {
		delete this;
	}
};
  • 只能建立在栈上
  • 如果使用new运算符的话,对象只会建立在堆上,只要私有了new运算符就可以实现
  • 或者可以重载new和delete,重载后函数体为空
class A {
private:
	void* operator new(size_t t) {}
	void* operator delete(void *ptr) {}
public:
	A(){}
	~A(){}
};

strcpy()手撕代码

char * strcpy(char *dst,const char *src) {
	assert(dst!=NULL && src!=NULL);	//先判断是否为空

	char *ret = dst;
	while((*dst++=*src++)!='\0');
	return ret;
}
  • 返回目标字符数组首地址的原因是可以满足链式表达式的书写,例:int len=strlen(strcpy(dst,src));
  • const修饰src防止函数修改源字符串
  • 该函数存在风险,如果原字符串的结束符不是’\0’,就会死循环;或者src的长度大于了dst的长度,就会非法访问

strncpy()手撕代码

char *strcpy(char *dst,const char *src,size_t len) {
	assert(dst!=NULL && src!=NULL);
	char *ret=dst;
	int offset=0;
	if(strlen(src)<len) {
		offset=len-strlen(src);
		len=strlen(len);
	}
	while(len--) {
		*dst++=*src++;
	}
	while(offset--) {
		*dst++='\0';
	}
	return ret;
}

memcpy()手撕代码

void * memcpy(void *dst,const void *src, size_t len) {
	if(NULL==dst || NULL==src)
		return NULL;
	void *ret=dst;
	if(dst<=src || (char *)dst>=(char *)src+len) {	//目标地址小于或者远大于源地址,可以正向复制
		while(len--) {
			*char *)dst=*(char *)src;
			dst=(char *)dst+1;
			src=(char *)src+1;
		}
	}
	else {	//内存重叠的情况从高地址复制
		src = (char *)src+len-1;
		dst = (char *)dst+len-1;
		while(len--) {
			*(char *)dst=*(char *)src;
			dst=(char *)dst-1;
			src=(char *)src-1;
		}
	}
	return ret;
}

strcmp()手撕代码

int strcmp(const char *str1,const char *str2) {
	while(*str1==*str2 && str1!='\0') {
		++str1;
		++str2;
	}
	return *str1-*str2;
}
  • 0相同,正数str1大,负数str2大

strcat()手撕代码

char* strcat(char *dst,const char *src) {
	char *ret=dst;
	while(dst!='\0') ++dst;
	while(src!='\0') {
		*dst++=*src++;
	}
	*dst='\0';
	return ret;
}
  • 字符串连接

strstr()手撕代码

char* strstr(const char *dst,const char *src) {
	assert(dst);
	assert(src);
	const char *tdst=dst;
	const char *tsrc=src;
	int i=0,j=0;//主串下标和子串下标
	while(i<=strlen(tdst)-1 && j<=strlen(tsrc)-1) {
		if(tdst[i]==tsrc[j]) {
			i++;
			j++;
		}
		else {
			i=i-j+1;
			j=0;
		}
	}
	if(j==strlen(tsrc)) {
		return tdst+i-strlen(tsrc);
	}
	return NULL;
}
  • 在str1中查找str2,返回str2在str1中的起始位置如果不存在则返回NULL

链表逆转

/*****
struct ListNode {
	int val;
	ListNode *next;
	ListNode(int x):val(x),next(NULL){}
}
*/
class Solution {
public:
	//就地逆置法
	ListNode* reverseList(ListNode *head) {
		ListNode *new_node=NULL;
		while(head) {
			ListNode *next=head->next;
			head->next=new_node;
			new_node=head;
			head=next;
		}
		return new_node;
	}
	//头插法
	ListNode* reverseList(ListNode *head) {
		ListNode tmp_node(0);
		while(head) {
			ListNode *next=head->next;
			head->next=tmp_node.next;
			tmp_node.next=head;
			head=next;
		}
		return tmp_node.next;
	}
};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值