C&&Cpp知识

8 篇文章 0 订阅

1 常见变量所占字节数

			643216char 		1Byte  		1Byte		1Byte
short int   2Byte  		2Byte		2Byte
int			4Byte 		4Byte		2Byte 	
float 		4Byte		4Byte		4Byte  	
double		8Byte  		8Byte 		8Byte 	
long		8Byte 		4Byte		4Byte 	
long long	8Byte		8Byte		8Byte  	

扩展小知识:字与字节
在16位的系统中(比如8086微机) 1字 (word)= 2字节(byte)= 16(bit)
在32位的系统中(比如win32) 1字(word)= 4字节(byte)=32(bit)
在64位的系统中(比如win64) 1字(word)= 8字节(byte)=64(bit)

指针变量:
指针变量存储的是地址,所以指针变量所占字节数与擦偶做系统的寻址空间有关,对于32位系统,寻址空间为0~232,所以占用4字节,对于64位系统,寻址空间为0-264, 所以占用8字节。

2 对某一位的清零、置1、获取、取反

#define setbit(x,y)  x |= (1<<y)
#define clrbit(x,y)  x &= ~(1<<y)
#define getbit(x,y)  ((x&(1<<y))>>y)
#define reversebit(x,y)  x ^= (1<<y)

原理:1或0/1都是1 0并上0/1都是0
1并0/1和0或0/1不改变值
0/1与1异或可以实现取反得到 1/0
~ 符号:按位取反
!符号:逻辑取反,可以把非0值变成0,把0值变为1
^ 符号:异或操作

3.动态存储方式与静态存储方式

静态存储区:
全局变量全部存储在静态存储区;程序开始执行时给全局变量分配存储区,程序执行完毕就释放。在程序执行过程中占据固定的存储单元。

动态存储区:
①函数形式参数
②函数中定义的没有用关键字static声明的变量
③函数调用时的现场保护和返回地址等存放在动态存储区
函数调用开始时分配,函数结束时释放。在程序执行过程中,这种分配和释放是动态的。

从变量的作用域的角度来观察,变量可以分为全局变量局部变量
全局变量都是存放在静态存储区中的。因此它们的生存期是固定的,存在于程序的整个运行过程
局部变量,如果不专门声明存储类别,都是动态地分配存储空间的

从变量值存在的时间(即生存期)观察,变量的存储有两种不同的方式:静态存储方式动态存储方式
静态存储方式是指在程序运行期间由系统分配固定的存储空间的方式
动态存储方式是在程序运行期间根据需要进行动态的分配存储空间的方式

扩展知识:

每一个变量和函数都有两个属性: 数据类型和数据的 存储类别
存储类别指的是数据在内存中存储的方式(如静态存储和动态存储)
存储类别包括: 自动的、静态的、寄存器的、外部的
根据变量的存储类别,可以知道变量的作用域和生存期
1.自动变量(auto变量)
局部变量,如果不专门声明存储类别,都是动态地分配存储空间的。调用函数时,系统会给局部变量分配存储空间,调用结束时就自动释放空间。因此这类局部变量称为自动变量,自动变量用关键字auto作存储类别的声明。
2.静态局部变量(static局部变量)
希望函数中的局部变量在函数调用结束后不消失而继续保留原值,即其占用的存储单元不释放,在下一次再调用该函数时,该变量已有值(就是上一次函数调用结束时的值),这时就应该指定该局部变量为“静态局部变量”,用关键字static进行声明。
用static 声明一个变量的作用是:
(1) 对局部变量用static声明,把它分配在静态存储区,该变量在整个程序执行期间不释放,其所分配的空间始终存在。
(2) 对全局变量用static声明,则该变量的作用域只限于本文件模块(即被声明的文件中)。
3. 寄存器变量(register变量)
一般情况下,变量(包括静态存储方式和动态存储方式)的值是存放在内存中的,寄存器变量允许将局部变量的值放在CPU中的寄存器中现在的计算机能够识别使用频繁的变量,从而自动地将这些变量放在寄存器中,而不需要程序设计者指定。
4.外部变量
一般来说,外部变量是在函数的外部定义的全局变量,它的作用域是从变量的定义处开始,到本程序文件的末尾。在此作用域内,全局变量可以为程序中各个函数所引用。

4. const、volatile、static关键字

static

  • 在函数中,用static修饰局部变量(静态局部变量),目的是希望在函数被调用后不立即释放其存储空间,在下一次再调用该函数时,该变量已有值(就是上一次函数调用结束时的值)。
  • 在函数外、模块内,对全局变量用static声明,则该变量的作用域只限于本文件模块(即被声明的文件中)。
  • 在模块内,被static修饰的函数只可被这一模块内的其它函数调用。

const关键字

const意味着只读,使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。

用法:
(1)修饰局部变量
const int a; int const a
都表示 a 是一个不可修改的常整型数

(2)常量指针(指针指向的内容是常量)
const int * a; int const * a
表示 a 是一个指向常整型数的指针,整型数是不可修改的,但指针可以
常量指针说的是不能通过这个指针改变变量的值。常量指针指向的值不能改变,但并不是说指针本身不能改变,常量指针可以指向其他的地址

(3)指针常量(常指针,指针本身是个常量)
int * const a
表示 a 是一个指向整型数的常指针,指针指向的整型数是可以修改的,但指针是不可修改的
指针常量指向的地址不能改变,但是地址中保存的数值是可以改变的,可以通过其他指向改地址的指针来修改

(4)指向常量的常指针
int const * const a
表示 a 是一个指向常整型数的常指针,指针和指针指向的整型数都是不可修改的

NOTE:如何区分常指针(指针常量)与常量指针
观察 * 与 const 的位置,将星号读作指针,const读作常量。

volatile关键字

volatile翻译过来是易变的意思,同样的被volatile修饰的变量表示此变量可能会意想不到的被改变,此时就要求在使用到此变量的时候,每次都需要从内存中重新读取此变量的值而不是使用保存在寄存器中的备份。有三个情景会使用到volatile关键字修饰:
(1)并行设备中的硬件硬件寄存器
(2)一个中断服务子程序中会访问到的非自动变量
(3)多线程应用中被几个任务共享的变量

NOTE:
前面提到const关键字表示只读,那么能同时使用const和volatile修饰同一个变量吗?答案是可以的,当一个为只读状态的状态寄存器,它可能会意想不到的被改变但程序不应该试图去修改它。volatile修饰符告诉编译器此变量的值可以以任何不被程序指明的方式改变,最常见的例子就是外部的端口值,它的变化可以不用程序内的任何的赋值语句就有可能被改变。const修饰的变量在程序里面是不能被改变的,但可以被程序外的东西修改,如果仅仅使用const,优化器可能对其进行改变,此时在使用volatile修饰即可保证万无一失。

5 结构体中的(.)与 (->)

点运算符(.)左边为结构体名称,箭头运算符(->)左边为结构体指针。

typedef struct          // 定义一个结构体类型:DATA  
{  
    char key[10];       // 结构体成员:key  
    char name[20];      // 结构体成员:name  
    int age;            // 结构体成员:age  
}DATA;  
      
DATA data;              // 声明一个结构体变量  
DATA *pdata;            // 声明一个指向结构体的指针  
      
// 访问数据操作如下:  
data.age = 24;          // 结构体变量通过点运算符(.)访问  
pdata->age = 24;        // 指向结构体的指针通过箭头运算符(->)访问

6 C语言程序的内存管理

对于一个C语言程序而言,内存空间主要由五个部分组成代码段(.text)数据段(.data)BSS段(.bss)组成,其中代码段,数据段和BSS段是编译的时候由编译器分配的,而堆和栈是程序运行的时候由系统分配的。布局如下
在这里插入图片描述
代码段:存放函数体的二进制的代码,由操作系统进行管理
数据段:存放已经初始化的全局变量和静态变量
BSS段:存放未初始化的全局变量和静态变量,我们一般在定义变量时如果不初始化,会默认初始化为0,所以没有必要单独将其初始化为0存储起来
栈:由编译器自动分配,存放函数的参数值以及局部变量(不包括static声明的变量)
堆:用于存放进程运行中被动态分配的内存段,由程序员使用代码申请和释放,若程序员不使用代码进行释放则在程序结束时由操作系统回收

  • 在程序运行前,也就是说程序在进行编译之后,生成了可执行文件,未执行前的程序分为两个区域,代码区和全局区。代码区,存放的是CPU执行的机器指令。代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令。全局区,存放静态变量和全局变量,全局区还包含了常量区, 字符串常量和其他常量也存放在此,该区域的数据在程序结束后由操作系统释放。
  • 程序运行之后,栈区由编译器自动分配自动释放(不要返回局部变量的地址),存放函数的参数和局部变量。堆区是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。

堆和栈的比较:
从申请方式,申请大小,申请效率简单比较:Stack的空间由操作系统自动分配/释放,Heap上的空间手动分配/释放。Stack空间有限,Heap是很大的自由存储区。Stack申请效率高,Heap申请效率低。

7 define与typedef的用法与不同

define 是宏定义, 其作用只是简单做一个字符串的替换
typedef 用于为现有的类型创建一个新的名字,或称作类型别名

两者的区别:
(1)原理及作用域不同
#define 是宏定义,在预编译阶段只做简单的字符串的替换,不做正确性检查。宏定义之后在程序的各个区域都可以使用。
typedef在编译时发挥作用,有类型检查的功能。在其作用域内给变量取一个别名。
(2)对指针的使用不用
使用 define 给指针取别名时,经const修饰后,指针可以修改,但指针指向的内容不能修改,且一次只能定义一个指针。(常量指针)
使用 typedef 给指针取别名时,经cosnt修饰后,指针不可以修改,但指针指向的内容可以修改,且一次能定义多个指针。(常指针,指针常量)

#define INT_P int * 
typedef int * int_p;

int a = 1, b = 2; 

const INT_P p1 = &a; // const int * p1 = int const * p1  常量指针
const int_p p2 = &b; // cosnt * int p2 常指针

int_p p3, p4;	//	p3 p4 都是 int 型的指针
INT_P p5, p6;	//  = int * p5, p6 只有一个是指针

8 输出格式

%m.n s
从要输出的字符串中取出 n 位,输出 m 列,如果 n<m,左边补0;若 n>m,输出n列。

%3.0f 中的0 表示输出小数点后0 位 如123.234 显示 123
%3.0f 中的3 表示输出这个数占用三列 如123 显示 123

9 运算符优先级与结合性

T0  	()	 [] 	. 	->
T1		++/--	~ 	!	-()	*/&(地址)	自右向左
T2		*	/	%
T3		+	-
T4		<<	>>
T5		>	>=	<	<=
T6		==	!=
T7		&
T8		^
T9		|
T10		&&
T11		||
T12		?:									自右向左
T13		=	/=	<<=	...(赋值运算符)			自右向左

10 左值与右值

  • 左值
    可以放在赋值符号左边的变量,即具有对应的可以由用户访问的存储单元,并且能够由用户去改变其值的量。左值表示存储在计算机内存的对象,而不是常量或计算的结果。或者说左值是代表一个内存地址值,并且通过这个内存地址,就可以对内存进行读并且写(主要是能写)操作;这也就是为什么左值可以被赋值的原因了。

  • 右值
    当一个符号或者常量放在操作符右边的时候,计算机就读取他们的“右值”,也就是其代表的真实值。简单来说就是,左值相当于地址值,右值相当于数据值。

一个典型的问题,为么 i++ 不能做左值,而 ++i 可以

int i = 0;
int *ip = &(i++); //错误
int *ip = &(++i); //正确

对于这个问题我们首先要看其实现原理,对于 i++

//后缀形式:
const int int::operator++(int) //函数返回值是一个非左值型的,与前缀形式的差别所在。
{//函数带参,说明有另外的空间开辟
  int oldValue = *this;  // 取回值
  ++(*this);  // 增加
  return oldValue;  // 返回被取回的值
}

因为i++返回的是编译器自动分配的临时变量oldValue ,而这个oldValue 并不是你程序中定义的可寻址变量的引用 ,也就是说你不能通过地址对它进行操作.(换句话说就是不能作为左值)。

对于 ++i :

// 前缀形式:
int& int::operator++() //这里返回的是一个引用形式,就是说函数返回值也可以作为一个左值使用
{//函数本身无参,意味着是在自身空间内增加1的
  *this += 1;  // 增加
  return *this;  // 取回值
}

++i 返回的是一个引用形式,就是说函数返回值也可以作为一个左值使用

11 数组指针与指针数组

  • 数组指针(行指针)
    数组指针是指一个指向数组的指针,其本质是一个指针,只不过这个指针指向的是一个数组。
    int (*p)[n];
()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。
大小:一个int型指针长度的空间
如要将二维数组赋给一指针,应这样赋值:
int a[3][4];
int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。
 p=a;        //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
 p++;       //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]
  • 指针数组
    指针数组是指一个数组元素是指针,其本质是一个数组,只不过数组中的元素是指针变量。
    int *p[n];
[]优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1时,则p指向下一个数组元素,这样赋值是错误的:p=a;因为p是个不可知的表示,只存在p[0]、p[1]、p[2]...p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样 *p=a; 这里*p表示指针数组第一个元素的值,a的首地址的值。
大小:n个int *的数据空间
如要将二维数组赋给一指针数组:
int *p[3];
int a[3][4];
p++; //该语句表示p数组指向下一个数组元素。注:此数组每一个元素都是一个指针
for(i=0;i<3;i++)
p[i]=a[i]
这里int *p[3] 表示一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2]
所以要分别赋值。

12 指针与引用

原文链接:https://blog.csdn.net/weixin_41256281/article/details/90545595

  • 指针从本质上讲是一个变量,变量的值是另一个变量的地址,指针在逻辑上是独立的,它可以被改变的,包括指针变量的值(所指向的地址)和指针变量的值对应的内存中的数据(所指向地址中所存放的数据)。
  • 引用从本质上讲是一个别名,是另一个变量的同义词,它在逻辑上不是独立的,它的存在具有依附性,所以引用必须在一开始就被初始化(先有这个变量,这个实物,这个实物才能有别名),而且其引用的对象在其整个生命周期中不能被改变,即自始至终只能依附于同一个变量(初始化的时候代表的是谁的别名,就一直是谁的别名,不能变)。

相同点:引用和指针在做参数及做返回值类型上几乎效率相同,但远远高于传值
不同点:

  1. 引用在定义时必须初始化,指针没有要求
  2. 引用只能初始化引用一个实体,而指针可以在任何时候指向任何一个同类型实体
  3. 没有NULL引用,但有NULL指针
  4. 在sizeof中含义不同:引用结果为引用类型的大小,指针始终是地址空间所占字节个数(32位:4字节 64位:8字节)
  5. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
  6. 有多级指针,但是没有多级引用
  7. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
  8. 引用比指针使用起来简洁、安全
  • 指针参数传递本质上是值传递,它所传递的是一个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,会在栈中开辟内存空间以存放由主调函数传递进来的实参值,从而形成了实参的一个副本(替身)。值传递的特点是,被调函数对形式参数的任何操作都是作为局部变量进行的,不会影响主调函数的实参变量的值(形参指针变了,实参指针不会变)。

  • 引用参数传递过程中,被调函数的形式参数也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参(本体)的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量(根据别名找到主调函数中的本体)。因此,被调函数对形参的任何操作都会影响主调函数中的实参变量。

  • 引用传递和指针传递是不同的,虽然他们都是在被调函数栈空间上的一个局部变量,但是任何对于引用参数的处理都会通过一个间接寻址的方式操作到主调函数中的相关变量。而对于指针传递的参数,如果改变被调函数中的指针地址,它将应用不到主调函数的相关变量。如果想通过指针参数传递来改变主调函数中的相关变量(地址),那就得使用指向指针的指针或者指针引用。

  • 从编译的角度来讲,程序在编译时分别将指针和引用添加到符号表上,符号表中记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值(与实参名字不同,地址相同)。符号表生成之后就不会再改,因此指针可以改变其指向的对象(指针变量中的值可以改),而引用对象则不能修改。

13 c++11新特性

1、指针、智能指针(nullptr、shared_ptr、std::weak_ptr)

  • (1)nullptr
  • (2)智能指针 shared_ptr、unique_ptr、weak_ptr

2、类型推导(auto、decltype)

  • (1)auto
  • (2)decltype
  • (3)拖尾返回类型

3、类特性修改

  • (1)默认函数行为(dafault、delete)
  • (2)构造函数(委托、继承构造函数using)
  • (3)显示控制虚函数重载(override、final)

4、STL容器

  • (1)std::array
  • (2)std::forward_list
  • (3)无序容器unordered_map、unordered_set
  • (4)元组std::tuple

5、多线程

  • (1)std::thread
  • (2)std::atomic
  • (3)std::condition_variable

6、其他

  • (1)for循环(区间迭代)
  • (2)匿名函数 lamda表达式
  • (3)初始化列表std::initializer_list
  • (4)正则表达式

14 构造函数与析构函数

  • 构造函数调用优先级:基类成员构造函数>基类构造函数>派生类成员构造函数>派生类构造函数
    析构函数反之

  • 为什么构造函数不能是虚函数?
    从存储空间角度,虚函数对应一个指向vtable虚函数表的指针,这大家都知道,可是这个指向vtable的指针其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数。
    从使用角度,虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调用。构造函数本身就是要初始化实例,那使用虚函数也没有实际意义呀。所以构造函数没有必要是虚函数。虚函数的作用在于通过父类的指针或者引用来调用它的时候能够变成调用子类的那个成员函数。而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。

  • 虚析构函数
    在使用基类指针操作派生类对象时,如果基类的析构函数不使用虚函数会造成内存泄露的现象。
    如果没有上述场景,不需要将析构函数定义为虚函数,因为这样会增加内存开销。当类里面有定义虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间。

#include<iostream>
using namespace std;
class ClxBase{
public:
	ClxBase() {};
	~ClxBase() { cout << "Output from the destructor of class ClxBase!" << endl; };
 
	void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
};
 
class ClxDerived : public ClxBase{
public:
	ClxDerived() {};
	~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };
 
	void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };
};
int main(){
    ClxBase *p = new ClxDerived;		// 基类指针操作派生类对象
	p->DoSomething();
	delete p;
	return 0;
}

上述代码的输出如下:显然没有调用派生类的析构函数

Do something in class ClxBase!
Output from the destructor of class ClxBase!

将基类的析构函数改为虚函数就不会产生上述问题了。

class ClxBase{
public:
	ClxBase() {};
	virtual ~ClxBase() { cout << "Output from the destructor of class ClxBase!" << endl; };
	void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
};

15 大小端的判断

uint32 check_little_endian()
{
	int i = 0x12345678;
	char *p = (char *)&i;
	return (*p == 0x78);
}
BOOL check_little_endian()
{
	union
	{
		char a;
		int  b;
	}u;
	
	u.b = 0x12345678;
	return (BOOL)(u.a == 0x78);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值