C语言基础
- 内部连接属性:
- 静态函数只能在声明它的源文件中使用。
- 静态全局变量不能被其他文件通过extern引用。
- static和全局变量都存储在静态存储区,在main运行前进行初始话的,生命周期为整个程序
- 和全局变量一样,默认初始化为0
- 在类中,static变量属于整个类,不属于某个对象,各个对象共享static变量
- 在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。
- 引用
- 引用是某个变量的别名,不能为空引用,必须初始化,一旦初始化,不能被改变
- 不需要分配内存空间
- 引用只有一级
- sizeof 引用得到的是所指向的变量(对象)的大小
- 引用访问一个变量是直接访问
- 引用在传参的时候只需把变量名传递即可
- 指针
- 指针是一个地址,可以为NULL,指向的空间可变
- 需要为指针分配空间
- 指针可以存在多级
- sizeof 指针得到的是指针本身的大小。
- 指针访问一个变量是间接访问。
- 指针在传参的时候需要把变量的地址传递过去
- 防止头文件被重复包含
- include<>编译器从标准库路径开始搜索file.h;
- include”“编译器先从当前目录下搜索file.h,找不到,再到标准库路径搜索file.h。
- 所谓“实时操作系统”,实际上是指操作系统工作时,其各种资源可以根据需要随时进行动态分配。由于各种资源可以进行动态分配,因此其处理事务的能力较强、速度较快。
- 全局变量:
- 存放在全局区(静态数据区)
- 声明在函数外部,作用域是当前整个源文件,生命周期是整个程序的开始和结束
- 对外部可见,可以被其他文件通过extern引用。(前提:非静态全局变量)
- 局部变量:
- 存放在堆栈
- 声明在函数内部,作用域是当前函数,生命周期是函数结束立即被释放
- 对外部不可见
- 左右子树都是平衡二叉树 且左右子树的深度差值的绝对值不大于 1。
- 没有回收垃圾资源,动态分配的资源空间没有及时释放,导致内存泄漏从而容易造成堆溢出(内存不足)
- 层次太深的递归调用。系统要在栈中不断保存函数调用时的现场和产生的变量,如果递归调用太深,就会造成栈溢出,这时递归无法返回。再有,当函数调用层次过深时也可能导致栈无法容纳这些调用的返回地址而造成栈溢出。
- 数组访问越界。C语言没有提供数组下标越界检查,如果在程序中出现数组下标访问超出数组范围,在运行过程中可能会内存访问错误。
- O(n²)
- 内联函数
- 构造函数
- 静态成员函数
- 队列:先进先出
- 栈:先进后出
- 不支持
- 字符串( string 和 char * )
- 实型 —实型变量分为单精度(float),双精度(double)和长双精度(long double)型
- 支持
- byte,char,short,int,long,bool,枚举类型。
- 可以,局部会屏蔽全局。要用全局变量,需要使用”::” 局部变量可以与全局变量同名, 在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量。对于有些编译器 而言,在同一个函数内可以定义多个同名的局部变量,比如在两个循环体内都定义 一个同名 的局部变量,而那个局部变量的作用域就在那个循环体内。
- extern关键字进行引用,假如变量名写错了,编译期间不报错,而在链接期间报错
- 通过头文件进行引用,加入变量名写错了,编译器期间报错
- 不可以,因为全局变量具有外部链接属性,需要static关键字把它的定义改为内部链接才可以。这样链接才不会出错
- 无限循环,和while(1)一样
- 前者:先执行一次循环体,再判断条件是否成立
- 后者:先判断条件是否成立,再执行循环体
- static全局变量:内部链接属性
- 作用域:当前定义该变量的源文件
- 不能被外部其他文件extern引用
- 普通的全局变量:外部链接属性
- 作用域:整个项目
- 可以被外部其他文件extern引用
- 总结:
- 存储方式相同(都是静态存储方式)
- 生命周期不同
- static局部变量:
- 存储在静态区
- 默认初始化为0,只被初始化一次
- 生命周期为整个程序
- 普通局部变量
- 存储在堆栈
- 默认初始化为随机值
- 函数调用结束立即被释放内存空间
- 总结:
- 存储方式不同
- 生命周期不同
- 默认初始值不同
- 在函数的返回类型前加上关键字static,函数就被定义成为静态函数
- 类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。
- 普通函数的定义和声明默认情况下是extern的,但静态函数不能被其他文件所用。
- static 函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝
- 一个C/C++程序编译时内存分为5大存储区:堆区、栈区、全局区(静态区)、文字常量区、程序代码区。
- 程序的局部变量存在于栈中,全局变量和静态变量存在于静态存储区中,动态申请数据存在于堆中,字符串存在于文字常量区,函数体的二进制代码存在于程序代码区
- 分配方式
- 栈:由系统自动分配,系统自动回收
- 堆:由程序员手工分配,也要手动释放
- 申请后系统的响应
- 栈:只要栈的剩余空间大于申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
- 堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该 结点从空闲结点链表中 删除,并将该结点的空间分配给程序。另外,对于大多数系统,会在这块内存空间中的首地址 处记录本次分配的大 小,这样代码中的 delete 语句才能正确的释放本内存空间。另外,由 于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空 闲链表中。
- 申请大小的限制
- 栈:在 Windows 下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话 的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在Windows下,栈的大小是 2M(也有的说是 1M,总之 是一个编译时就确定的常数),如果申请的空间超过栈的剩余空 间时, 将提示 overflow。因此,能从栈获得的空间较小。
- 堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储 的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受 限于计算机系统中有效的 虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
char s1[]="aaaaaaaaaaaaaaa"; //是在运行时刻赋值的;
char *s2="bbbbbbbbbbbbbbbbb";//是在编译时就确定的;
- 预编译又称为预处理,是做些代码文本的替换工作。处理#开头的指令,比如拷贝#include 包含的文件代码,#define 宏定义的替换,条件编译等,
- c中为const分配内存,而c++不会为const分配内存空间,只有对const变量取址操作或者把该变量定义为extern才会分配内存
- c中const默认为外部连接,c++中const默认为内部连接.当c语言两个文件中都有const int a的时候,编译器会报重定义的错误。而在c++中,则不会,因为c++中的const默认是内部连接的。如果想让c++中的const具有外部连接,必须显示声明为: extern const int a = 10;
const int a; //a是一个常整型数
int const a; //和第一个是一样的
const int *a; //指向常整型数的指针,指针指向的整型数是不可修改的,但指针可以修改的
int * const a;//指向整型数的常指针,指针指向的整型数是可以修改的,但指针是不可修改
int const * a const;//指向常整型数的常指针,指针指向的整型数是不可修改,同时指针也是不可修改
- Volatile修饰符告诉编译程序不要对该变量所参与的操作进行优化。
- 并行设备的硬件寄存器(如:状态寄存器)
- 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
- 多线程应用中被几个任务共享的变量
- 按照数据结构类型的不同,将数据模型划分为层次模型、网状模型和关系模型。
- 结构体:每个成员都有一块独立的内存地址,对其它成员赋值互不影响
- 联合体:所有成员共用一块内存地址,对其它成员进行赋值会对其它成员进行重写
- 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。
- 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配 运算内置于处理器的指令集。
- 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用 free 或 delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。
- const有类型,可进行编译器类型安全检查。#define无类型,不可进行类型检查.
- const有作用域,而#define不重视作用域,默认定义处到文件结尾.如果定义在指定作用域下有效的常量,那么#define就不能用。
- 有些集成化的调试工具可以对 const 常量进行调试,但是 不能对宏常量进行调试。
if ( !a ) or if(a) //BOOL
if ( a == 0) //int
const EXPRESSION EXP = 0.000001
if ( a < EXP && a >-EXP) //float
if ( a != NULL) or if(a == NULL) //pointer
#ifdef cplusplus
cout<<"c++";
#else
cout<<"c";
#endif
- 带参数的宏
- 处理时间:编译阶段
- 参数类型:没有参数类型的问题
- 处理过程:不分配内存
- 程序长度:变长
- 运行速度:不占运行时间
- 函数
- 处理时间:程序运行时
- 参数类型:定义实参、形参类型
- 处理过程:分配内存
- 程序长度:不变
- 运行速度:调用和返回占用时间
- 设 2 个栈为 A,B,一开始均为空。
- 入队:将新元素 push 入栈 A;
出队:
- 判断栈 B 是否为空;
- 如果为空,则将栈 A 中所有元素依次 pop 出并 push 到栈 B,将栈 B 的栈顶 元素 pop 出;
- 如果不为空,栈B直接出栈。
总结:用两个栈A和B模拟一个队列时,A作输入栈,逐个元素压栈,以此模拟队列元素的入队。当需要出队时,将栈A退栈并逐个压入栈B中,A中最先入栈的元素,在B中处于栈顶。B退栈,相当于队列的出队,实现了先进先出。显然,只有栈s2为空且s1也为空,才算是队列空。
- [算法讨论] 算法中假定栈A和栈B容量相同。出队从栈B出,当B为空时,若A不空,则将A倒入B再出…
这样实现的队列入队和出队的平摊复杂度都还是 O(1)。
- 设 2 个栈为 A,B,一开始均为空。
- 入栈:将元素进队列A
- 出栈:
- 判断队列A中元素的个数是否为1,如果等于1,则出队列,否则将队列A中的元素依次出队列并放入队列B,直到队列A中的元素留下一个,然后队列A出队列,再把队列B中的元素出队列以此放入队列A中。
for(;;){}
while(1){}
goto Loop:
#define BIT3 (0x1 << 3)
//0x0000 0001 原来的值
//0x0000 1000 左移后的值
static int a;
void set_bit3(void)
{
//a 0x0000 0000
//BIT3 0x0000 1000 |
//a 0x0000 1000
a |= BIT3;
}
void clear_bit3(void)
{
//a 0x0000 0000
//~BIT3 0x1111 0111 &
//a 0x0000 0000
a &= ~BIT3;
}
int main(void)
{
set_bit3();
printf("%d\n", a);//8
clear_bit3();
printf("%d\n", a);//0
system("pause");
return 0;
}
//第一种方法(推荐)
{
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa66;
}
//第二种方法
{
*(int * const)(0x67a9) = 0xaa55;
}
//中断服务子程序(ISR),关键字interrupt
interrupt double compute_area(double radius)
{
double area = PI * radius * radius;
printf("\nArea = %f", area);
return area;
}
1) ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。
2) ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。
3) 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。(浮点数还不可以进行位运算)
4) 与第三点一脉相承,printf()经常有重入和性能上的问题。
从三个方面分析
- 内存碎片
- 碎片收集
- 变量的持行 时间等等
//下面的代码片段的输出是什么,为什么?
char *ptr;
if ((ptr = (char *)malloc(0)) == NULL)
puts("Got a null pointer"); //最后输出这句
else
puts("Got a valid pointer");
//下面哪种方法更好
#define dPS struct s *
typedef struct s * tPS;
//定义变量
dPS p1,p2;
tPS p3,p4;
//展开后
struct s * p1, p2;
//p1 为一个指向结构的指针,p2为一个实际的结构,这也许不是你想要的。 第二个例子正确地定义了 p3 和 p4 两个指针。
- 一个整型数(An integer)
- int a
- 一个指向整型数的指针(A pointer to an integer)
- int *a
- 一个指向指针的的指针,它指向的指针是指向一个整型数(Apointer to a pointer to an integer)
- int **a
- 一个有 10 个整型数的数组(An array of 10 integers)
- int a[10]
- 一个有 10 个指针的数组,该指针是指向一个整型数的(An array of10 pointers to
integers)
- int *a[10]
- 一个指向有 10 个整型数数组的指针(A pointer to an array of 10 integers)
- int (*a)[10]
- 一个指向函数的指针,该函数有一个整型参数并返回一个整型数
(A pointer to a function that takes an integer as an argument and returns an integer)
- int (*a)(int)
- 一个有 10 个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个 整型数(An array of ten pointers to functions thattake an integer argument and return an integer)
- int (*a[10])(int)
//两个数交换
#define SWAP(x,y) (x)=(x)+(y);(y)=(x)-(y);(x)=(x)-(y);
//两个数比较大小
#define MAX(x,y) (x)>(y)?(x):(y)
//1 年中有多少秒(忽略闰年问题)
#define SECONDS_PER_YEAR (365*24*60*60)
//求出一个数组的元素个数
#define NTBL(table) (sizeof(table)/sizeof(table[0]))
- 不会,存在于全局静态区(两个不同的变量,地址不同)
- 将这个指针指向的 next 节点值 copy 到本节点,将本节点 next 指向 next->next,并随后删除原 next 指向的节点。(不是真正意义上的删除本节点,只是将本节点的下一个节点对本节点的覆盖而已)
- 有时单元之间有依赖关系,比如单元A依赖单元B,所以单元B要先于单元A编译。
可以用#pragma startup 指定编译优先级,但如果使用了#pragma package(smart_init) 编译器就会根据优先级的大小先后编译。
- 对齐规则
- CPU寻址是以周期为单位,
- Linux 32位最大以4字节对齐
- Linux 64位最大以8字节对齐
- vs最大对齐单位为8字节
- 注:需要结构体的最大成员和CPU架构(32位或64位)进行对比,取小的作为对齐单位
- 指令设置字节对齐规则
- 一般在VC++中加上#pragma pack(n)设置内存对齐。
- #pragma pack(1) <——->()里只能填1、2、4、8、16、32、64……中的一个。
- #pragma pack(n) 编译器将按照n字节对齐
- #pragma pack() 编译器将取消自定义字节对齐方式
- 在#pragma pack(n)和#pragma pack()之间的代码按n字节对齐。
- 注:尽管如此,但还是需要结构体最大成员和CPU架构(32位或64位)进行对比,取小的作为对齐单位
- 字节对齐目的:以空间换时间
- 存储规则
- 第1个成员,放的偏移量的位置是0的位置,以后每个数据成员存放的起点,必须是该成员类型大小的整数倍
- 整体大小必须的对齐单位的整数倍,不足要补齐
- 结构体作为成员的对齐规则
- 如果一个结构体B里嵌套另一个结构体A,还是以规则1找对齐单位,但是结构体A存储起点为A内部对齐单位整数倍的地方,
- (struct B里存有struct A,A里有char,int,double等成员,那A应该从8的整数倍开始存储(64位架构)),结构体A中的成员的对齐规则仍满足规则1、规则3。
- 注意:
- 结构体A所占的大小为该结构体成员内部最大元素的整数倍,不足补齐。
- 不是直接将结构体A的成员直接移动到结构体B中。
- 大端模式:认为第一个字节是最高位字节,也就说按照从低地址到高地址的顺序存放数据的高位字节到低位字节。
- 小端模式:认为第一个字节是最低位字节,也就是说按照从低地址到高地址的顺序存放数据的低位字节到高位字节。
举例:
内存地址:0x0000 0x0001 0x0002 0x0003
对应数据: 0x12 0x34 0x56 0x78
如果我们去读取一个地址为0x0000的4字节变量
若字节序位为大端模式,读出为:0x12345678
若字节序位为小端模式,读出为:0x78563412
- 优点:
- inline定义的内联函数,函数代码被放入符号表中,在使用时进行替换(像宏一样展开),效率很高。
- inline可以作为类的成员函数,可以使用所在类的保护成员及私有成员。
- 区别:
- 内联在编绎时展开,宏在预编译时展开。 展开的时间不同。
- 编译内联函数可以嵌入到目标代码,宏只是简单文本替换。
- 内联会做类型,语法检查,而宏不具这样功能。
- 宏不是函数,inline函数是函数
- 宏定义小心处理宏参数(一般参数要括号起来),否则易出现二义性,而内联定义不会出现。
- 内联函数只是一种建议,是否按内联处理,看编译器,有些简洁的函数就算不定义内联,编译器也会按内联处理
- 一个接口实现多个方法
- 条件
- 必须定义虚函数
- 要有继承
- 父类指针指向子类对象
- 引用就是某个变量的别名,引用一旦初始化就不能再把该引用名作为其他变量的别名
引用本身不是数据类型,不占存储单元,不能建立数组的引用
其实它是有空间的:VC++6.0上的处理是给引用分配一个指针大小的空间,用以存放被应用变量的地址
- 不能返回局部变量的引用
在内存中不产生返回值的副本(导致返回局部变量的引用是错误的,因为局部变量的生命周期结束,引用也会失效)- 不能返回函数内部new分配的内存的引用
被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,
那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。
- 可以被连续使用,赋值操作符的返回值必须是一个左值,以便可以被继续赋值,
- 可选的其它方案包括:返回一个流对象和返回一个流对象指针。
- 对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<<操作符实际上是针对不同对象的!这无法让人接受。
- 对于返回一个流指针,则不能连续使用<<操作符。
因此,返回一个流对象引用是唯一的选择
答:引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可以指向它的派生类实例
- 可选的方案包括:
- 返回一个对象
- 返回一个局部变量的引用
- 返回一个new分配的对象的引用
- 返回一 个静态对象引用
根据前面提到的引用作为返回值的三个规则,第2、3两个方案都被否决了。
静态对象的引用又因为((a+b) == (c+d))会永远为true而导致错误。
所以可选的只剩下返回一个对象了。
- 全局变量前面加上static就变成了全局变量。全局变量本身就是静态存储方式,静态全局变量也是静态存储方式。
- 两者的区别
- 非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态全局变量在各个源文件中均可访问。
- 而静态全局变量只有在定义该静态全局变量的文件中可用,在同一源程序的其他源文件中不可访问。因此可避免在其他源文件中的引起错误。
注意:把局部变量变为静态变量后,改变了其生命周期。把全局变量变为静态全局变量,改变了其作用域,限制了其使用范围
- 封装:将客观事物抽象成类,每个类对自身的数据和方法实行protection(private, protected,public)
- 继承:更好的代码复用
- 多态:一个方法实现多种形态
- 重载:同名函数但是:参数个数不同,参数类型不同,参数顺序不同,对于重载函数的调用,在编译器间就已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!解决了取名的烦恼和增加了代码的可读性
- 重写:是指子类重新定义父类虚函数的方法。和多态真正相关,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚绑定)。
答:1)隐藏实现细节,使得代码能够模块化,扩展代码模块,实现代码重用
2)接口重用,为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性被正确调用
//Linux编译时 g++ std=c++11 *.cpp
#include
#include
#include
using namespace std;
//线程数
#define THREAD_NUM 100
class Singleton
{
public:
//单例回收
class Garbo
{
public:
~Garbo()
{
if(smInstance!=nullptr)
{
delete smInstance;
}
}
};
private:
static Garbo smGarbo;
static mutex smMutex;
static Singleton *smInstance;
Singleton(const Singleton &other);
Singleton()
{
++smInstanceCount;
cout<<"Singleton constructor"<cout<<"Singleton destructor"<public:
static int smInstanceCount;
static Singleton *getInstance()
{
//双重判断加锁
if(smInstance == nullptr)
{
lock_guard lock(smMutex);
if(smInstance==nullptr)
{
smInstance = new Singleton;
}
}
return smInstance;
}
};
Singleton *Singleton::smInstance = nullptr;
mutex Singleton::smMutex;
Singleton::Garbo Singleton::smGarbo;
int Singleton::smInstanceCount = 0;
class MyThreadRun
{
public:
void run() {
//每个线程只调用getInsgtance();
Singleton::getInstance();
}
void thread_run() {
thread *arr[THREAD_NUM];
//产生N个线程对象
for (int i = 0; i < THREAD_NUM; ++i) {
thread *t = new thread(&MyThreadRun::run, this);
arr[i]=t;
}
//join 线程
for (int i = 0; i < THREAD_NUM; ++i) {
arr[i]->join();
}
//释放线程对象
for (int i = 0; i < THREAD_NUM; ++i) {
delete arr[i];
arr[i]=nullptr;
}
}
};
int main()
{
MyThreadRun m;
m.thread_run();
if(Singleton::smInstanceCount!=1)
{
cerr<<"error : singleton count is "<< Singleton::smInstanceCount<return -1;
}
//Singleton *s = Singleton::getInstance();
//Singleton s2 = *s;
return 0;
}