new操作符从自由存储区上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。
使用new操作符申请内存分配时无须指定内存块的大小,而malloc则需要显式地指出所需内存的尺寸。
int *p = new int;
delete p;//一定要配对使用new 和 delete
int *p = new int [10];//对于数组,应使用另一种格式的delete来释放
delete [] p;
int *p = (int *)malloc(sizeof(int));
*p = 100;
free(p);
p = NULL;
return 0;
malloc 与 free 是 C++/C 语言的标准库函数,new/delete 是 C++的运算符。它们都可用于申请动态内存和释放内存。 对于非内部数据类型的对象而言,
光用 maloc/free 无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free 是库函数而不是运算符,
不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加malloc/free。 因此 C++语言需要一个能完成动态内存分配和初始化工作的运算符 new,
以及一个能完成清理与释放内存工作的运算符 delete。注意 new/delete 不是库函数。
delete只会调用一次析构函数,而delete[]会调用每一个数组元素的析构函数。
delete与new配套,delete []与new []配套。
在支持虚拟内存管理的操作系统上:当请求的内存大于128kb,malloc()会调用mmap()分配一起分配内存。
在不支持虚拟内存管理的实时操作系统上运行:这种情况下,在只有1G物理内存的计算机上malloc(1.2G)就会失败。
定义一个指针p,打印出sizeof( p ),如果节后是4,则表示该操作系统是32位,打印结果是2,表示是16位。
在计算机中,有些信息存储时并不需要占用一个完整的字节,而只需占用一个或几个二进制位。比如在存放一个只有0和1两种状态的开关量时,
用一位二进制位即可表示。因此,为了节省存储空间,C语言提供了一种称为“位域”的数据结构来充分利用存储空间。
位与符号是一个&,两个&&是逻辑与,位或符号是一个|,两个||是逻辑或。
C语言中按位取反是~,C语言中的逻辑取反是!。
左移位<<与右移位>>
对于无符号数,左移时右侧补0(相当于逻辑移位),右移时左侧补0(相当于逻辑移位)
对于有符号数,左移时右侧补0(叫算术移位,相当于逻辑移位),右移时左侧补符号位(如果正数就补0,负数就补1,叫算术移位)
嵌入式中研究的移位,以及使用的移位都是无符号数.
https://blog.csdn.net/weixin_49303682/article/details/118662325
int *p;
p = (int *)0x67a9;
*p = 0xaa66;
#define BIT3 (0x1<<3)
static int a;
void set_bit3(void)
{
a |= BIT3;
}
void clear_bit3(void)
{
a &= ~BIT3;
}
左值:能对表达式取地址,一般指表达式结束后依然存在的持久对象。
右值:不能对表达式取地址,一般表达式结束后就不再存在的临时对象。
x++的效率最高,直接去出来+1后在放回原来的地址。
x=x+1最低, 因为执行步骤如下: <1>读取右x的地址; <2>x+1; <3>读取左x的地址,<4>将右值传给左边的x(编译器并不认为左右两边的x的地址相同)。
一个整型数 int a;
一个指向整型数的指针 int *a;
一个指向指针的指针,它指向的指针式指向一个整型数 int **a;
一个有10个整型数的数组 int a[10];
一个有10指针的数组,该指针是指向一个整型数 int *a[10];
一个指向有10个整型数数组的指针 int (*a)[10];
一个指向函数的指针,该函数有一个整型数参数并返回一个整型数 int (*a)(int);
一个有10个指针的数组,该指针指向一个函数,该函数有一个整型数参数并返回一个整型 int (*a[10])(int);
C语言是一种强类型的程序设计语言,int x,y,z;在C程序中,每一个变量都必须声明其取值类型。
char 占8位1字节,short占16位2字节,int 占32位4字节,long 占32位4字节,float占32位4字节,double占64位8字节。
从长字节数据类型转换为短字节数据类型,会产生截断:从4字节的int类型转换成1个字节的char类型,则取int数据的最低的一个字节。
从短字节类型转换为长字节类型:从char转换为int:则在前面的三个字节补符号位0x000000。
float:1bit(符号位)+8bit(指数位)+23(尾数位) 2^23=8388608,共7位,意味着最多有7位有效数字。
double:1bit(符号位)+11bit(指数位)+52bit(尾数位) 2^52=4503599627370496. 一共16位,同理double的精度为15~16位。
#include
int main(void)
{
float m = (float)3.14;
double n = 10.5;
printf("%f,%lf\n",m,n);
return 0;
}
输出:3.140000,10.500000 因为%lf只保留小数点后6位。
char beta; beta=’ab’; 是错误的,改正:char beta; beta=’ab/’
char beta; beta=”a”; 是错误的,改正:char beta; beta=’a’
定义字符串数组:char a[ ] = "abcse"
非图形字符,如退格,换行,也可以表示成字符型常量。表示方法是使用转义字符”/”;
已知char b[5],*p=b; ,则正确的赋值语句是 C
A. b=“abcd” ; B. *b=“abcd”; C. p=“abcd”; D. *p=“abcd”;
下列可以正确表示字符型常量的是 D
A、297 B、"a" C、"\n" D、'\t'
bool型数据:
if( flag ) { A; } else { B; }
int型数据:
if( 0 != flag ) { A; } else { B; }
指针型数:
if( NULL == flag ) { A; } else { B; }
if(x>0.000001&&x<-0.000001)
%d
, %ld
, %lld
, %lf
, %f
%d=int,
%ld=long,
%lld=long long;
在32位编译器上,int=long=32bit;long long=64bit。
输入时:
float 输入用 %f,小数点后6位。
double 输入用 %lf,小数点后6位。
printf(“%d\n”,101010); 默认的方式是左对齐。
printf(“%-10d\n”,101010); 在打印数字宽度前面加一个“-”也是左对齐,10表示数字宽度为10,其他空格补齐。
printf(“%10d”,101010); 在%和d之间加上数字宽度,就可以右对齐了,10表示数字宽度为10,其他空格补齐。
double a = 326.3845;
printf("%10.3f\n", a);
结果是:
326.385(注意前面是有空格的)
printf("%04d", 3);
结果为:
0003
while(1)
{
;}
for(;;)
{
;}
#include "stdio.h"
void main()
{
int n = 2, k = 0;
while(k++&&n++>2);
printf("%d %d\n",k,n);
}
输出的正确答案是1 2
刚开始运算的时候,k的值是0,n的值是2,并还没有进行自增1的操作,自增1要在下一次循环中。对于循环条件(k++&&n++>2),
首先计算k,由于k的初值为0,k++是后坐,因此k++在参与比较的时候是0(假),比较以后k值增1(变为1),这时,系统不会再计算&&后面的表达式了(即
所谓的惰性计算方法),所以,n++>2没有参与运算,也就是说,循环语句执行之后,k的值是1,n的值是2.
为变量分配地址和存储空间的称为定义,不分配地址的称为声明。一个变量可以在多个地方声明,但是只在一个地方定义。
加入extern修饰的是变量的声明,说明此变量将在文件以外或在文件后面部分定义。
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
#define MIN(a, b) (a) <= (b) ? (a) : (b)
sizeof的参数可以是数据的类型,也可以是变量,而strlen只能以结尾为‘\0‘的字符串作参数。
sizeof计算的是数据类型占内存的大小,而strlen计算的是字符串实际的长度。
static在C语言中具有:记忆性,隐藏性,初始化为0的特点。
而C++中除了上述功能外,还用来定义类的成员变量和函数。即静态成员和静态成员函数。
C++函数的三种传递方式为:值传递、指针传递和引用传递。
比如全局变量的初始化,就不是由main函数引起的。举例:
class A{};
A a; //a的构造函数限执行
int main() {}
面向对象可以理解成对待每一个问题,都是首先要确定这个问题由几个部分组成,而每一个部分其实就是一个对象。
然后再分别设计这些对象,最后得到整个程序。传统的程序设计多是基于功能的思想来进行考虑和设计的,而面向对象的
程序设计则是基于对象的角度来考虑问题。这样做能够使得程序更加的简洁清晰
编程规范可总结为:程序的可行性,可读性、可移植性以及可测试性。
说明:这是编程规范的总纲目,面试者不一定要去背诵上面给出的那几个例子,应该去理解这几个例子说明的问题,
想一想,自己如何解决可行性、可读性、可移植性以及可测试性这几个问题,结合以上几个例子和自己平时的编程习惯来回答这个问题。
面向对象的三大特征是封装性、继承性和多态性:
封装性:将客观事物抽象成类,每个类对自身的数据和方法实行protection(private, protected,public)。
继承:继承主要实现重用代码,节省开发时间,子类可以继承父类的属性和方法。
多态性:是将父类对象设置成为和一个或更多它的子对象相等的技术。用子类对象给父类对象赋值之后,父类对象就
可以根据当前赋值给它的子对象的特性以不同的方式运作。
编译器发现一个类中有虚函数,便会立即为此类生成虚函数表 vtable。虚函数表的各表项为指向对应虚函数的指针。
编译器还会在此类中隐含插入一个指针vptr(对vc编译器来说,它插在类的第一个位置上)指向虚函数表。调用此类的构造函数时,
在类的构造函数中,编译器会隐含执行vptr与vtable的关联代码,将vptr指向对应的vtable,将类与此类的vtable联系了起来。
另外在调用类的构造函数时,指向基础类的指针此时已经变成指向具体的类的this指针,这样依靠此this指针即可得到正确的vtable,。
如此才能真正与函数体进行连接,这就是动态联编,实现多态的基本原理。
主要是两个:(1)隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用;
(2)接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时的正确调用。
多态:是对于不同对象接收相同消息时产生不同的动作。
虚函数:在基类中冠以关键字 virtual 的成员函数。 它提供了一种接口界面。允许在派生类中对基类的虚函数重新定义。
纯虚函数的作用:在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行定义。作为接口而存在,纯虚函数不具备函数的功能,一般不能直接被调用。
具有纯虚函数的类是抽象类(abstract class),不能声明对象,只能作为基类为派生类服务,除非派生类完全实现了基类的所有纯虚函数,否则派生类也成为抽象类,不能
声明对象。
重载 | 同一名字子空间 | 是指允许存在多个同名函数,而这些函数的参数表不同。 |
重定义/隐藏 | 不同名字子空间 | 用于继承,派生类与基类的函数同名,屏蔽基类的函数 |
重写/覆盖 | 不同名字子空间 | 用于继承,子类重新定义父类虚函数的方法 |
不能被重载的运算符
在 C++运算符集合中,有一些运算符是不允许被重载的。这种限制是出于安全方面的考虑,可防止错误和混乱。
(1)不能改变 C++内部数据类型(如 int,float 等)的运算符。
(2)不能重载‘.’,因为‘.’在类中对任何成员都有意义,已经成为标准用法。
(3)不能重载目前 C++运算符集合中没有的符号,如#,@,$等。原因有两点,一是难以理解,二是难以确定优先级。
(4)对已经存在的运算符进行重载时,不能改变优先级规则,否则将引起混乱。
template
class A
{
friend T;
private:
A() {}
~A() {}
};
class B : virtual public A
{
public:
B() {}
~B() {}
};
void main( void )
{
B b;
return;
}
构造函数不可以,析构函数可以。
因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。
如果它的构造函数是虚函数,那就要通过对象中的虚函数表指针来调用,而这个虚函数表是在构造函数初始化列表阶段才初始化的。
https://blog.csdn.net/qq_41830537/article/details/102626648
const修饰的变量是只读的,本质上还是变量,与C语言不同,c++中的const不是只读变量。C++中的const是个真正意义上的常量。
C语言中的const还是可变的:
int main()
{
const int c = 0;
int* p = (int*)&c;
printf("Begin...\n");
*p = 5;
printf("c = %d\n", c); //C语言编译:输出5,
//说明c并不是真正意义上的常量
return 0;
}
由于c++中const常量的值在编译期就已经决定,下面的做法是OK的,但是c中是编译通不过的。
int main(void)
{
const int a = 8;
const int b = 2;
int array[a+b] = {0};
return 0;
}
(1)内联(inline)函数是C++中引用的一个新的关键字;C++中推荐使用内联函数来替代宏代码片段;
(2)内联函数将函数体直接扩展到调用内联函数的地方,这样减少了参数压栈,跳转,返回等过程;
(3) 由于内联发生在编译阶段,所以内联相较宏,是有参数检查和返回值检查的,因此使用起来更为安全;
(4)内联函数不能过于复杂,最初C++限定不能有任何形式的循环,不能有过多的条件判断,不能对函数进行取地址操作等,
但是现在的编译器几乎没有什么限制,基本都可以实现内联。
用法不同:typedef用来定义一种数据类型的别名,增强程序的可读性。define主要用来定义常量,以及书写复杂使用频繁的宏。
执行时间不同:typedef是编译过程的一部分,有类型检查的功能。define是宏定义,是预编译的部分,其发生在编译之前,只是简单的进行字符串的替换,不进行类型的检查。
https://blog.csdn.net/qq_45607873/article/details/123746610
strcpy函数会导致内存溢出。
strcpy拷贝函数不安全,他不做任何的检查措施,也不判断拷贝大小,不判断目的地址内存是否够用。
char *strcpy(char *strDest,const char *strSrc)
strncat()主要功能是在字符串的结尾追加n个字符。
char * strncat(char *dest, const char *src, size_t n);
strcat()函数主要用来将两个char类型连接。例如:
char d[20]="Golden";
char s[20]="View";
strcat(d,s); //打印d printf("%s",d);
输出 d 为 GoldenView (中间无空格)
**延伸**
:
memcpy拷贝函数,它与strcpy的区别就是memcpy可以拷贝任意类型的数据,strcpy只能拷贝字符串类型。
memcpy 函数用于把资源内存(src所指向的内存区域)拷贝到目标内存(dest所指向的内存区域);
有一个size变量控制拷贝的字节数;
函数原型:
void *memcpy(void *dest, void *src, unsigned int count);
队列和栈都是线性存储结构,但是两者的插入和删除数据的操作不同,队列是“先进先出”,栈是“后进先出”。
堆存放动态分配的对象——即那些在程序运行时动态分配的对象,比如 malloc出来的对象,其生存期由程序控制,必须手动申请,手动释放。
栈用来保存定义在函数内的非static对象,如局部变量,仅在其定义的程序块运行时才存在,由编译器自动创建和销毁。
总的来说,堆是C语言和操作系统的术语,是操作系统维护的一块动态分配内存;自由存储是C++中通过new与delete动态分配和释放对象的抽象概念。他们并不是完全一样。
从技术上来说,堆(heap)是C语言和操作系统的术语。堆是操作系统所维护的一块特殊内存,它提供了动态分配的功能,当运行程序调用malloc()时就会从中分配,
稍后调用free可把内存交还。而自由存储是C++中通过new和delete动态分配和释放对象的抽象概念,通过new来申请的内存区域可称为自由存储区。
用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元即为内存泄露。
malloc的时候得确定在那里free. new的时候得确定delete。在C++中应该优先考虑使用智能指针.
存储形式:数组是一块连续的空间,声明时就要确定长度。链表是一块可不连续的动态空间,长度可变,每个结点要保存相邻结点指针。
数据查找:数组的线性查找速度快,查找操作直接使用偏移地址。链表需要按顺序检索结点,效率低。
数据插入或删除:链表可以快速插入和删除结点,而数组则可能需要大量数据移动。
越界问题:链表不存在越界问题,数组有越界问题。
在使用结构变量时要先对其定义; 结构名是结构的标识符不是变量名;结构是按变量名字来访问成员的;结构体指针采用“->”访问成员,结构体变量采用“.”访问。
结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合中只存放了一个被选中的成员(所有成员共用一块地址空间), 而结构的
所有成员都存在(不同成员的存放地址不同)。
对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于结构的不同成员赋值是互不影响的。
引用就是某个目标变量的“别名”(alias),对应用的操作与对变量直接操作效果完全相同。
声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用
分配存储单元。不能建立数组的引用。
传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的
操作就是对其相应的目标对象(在主调函数中)的操作。
使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作.
vector单端数组,当现有的内存空间不够装下数据时,首先配置一块新的空间,然后将旧空间的数据搬往新空间,再释放原来的空间。
deque双端数组,可以在头尾两端分别做元素的插入和删除操作,除非必要,应尽可能的选择使用vector而非deque,因为 deque的迭代器比vector的迭代器要复杂的多。
list双向链表,相较于vector的连续线性空间,list就显得负责许多,它的好处是每次插入或者删除一个元素,就是配置或者释放一个元素的空间,适合频繁的不确实位置元素
的移除插入。
set二叉树,map二叉树,底层实现都是 红黑树,它可以在O(logn)
时间内高效的做查找,插入和删除。
int main()
{
int j=2;
int i=1;
if(i = 1) j=3;
if(i = 2) j=5;
printf("%d",j);
}
输出为5;如果再加上if(i=3)j=6;则输出6。
int main(void)
{
unsigned int a = 6;
int b = -20;
char c;
(a+b>6)?(c=1):(c=0);
}则c=1,但a+b=-14;如果a为int类型则c=0。
原来有符号数和无符号数进行比较运算时(==,<,>,<=,>=),有符号数隐式转换成了无符号数(即底层的补码不变,但是此数从有符号数变成了无符号数),
比如上面 (a+b)>6这个比较运算,a+b=-14,-14的补码为1111111111110010。此数进行比较运算时,
被当成了无符号数,它远远大于6,所以得到上述结果。
main()
{
int a[5]={1,2,3,4,5};
int *ptr=(int *)(&a+1);//&a相当于变成了行指针,加1则变成了下一行首地址
printf("%d,%d",*(a+1),*(ptr-1));
}
*(a+1)就是a[1],*(ptr-1)就是a[4],执行结果是2,5
int main()
{
char a;
char *str=&a;
strcpy(str,"hello");
printf(str);
return 0;
}
将字符串赋值给字符变量,内存不够,存在越界。
void test(void) {
char *p = null;
strcpy(p,"hello");
printf("%s\n",p);
}
段错误
野指针问题
wap( int* p1,int* p2 )
{
int *p;
*p = *p1;
*p1 = *p2;
*p2 = *p;
}
段错误,p没有空间,为其分配空间或者不使用指针
char *RetMenory(void)
{
char p[] = “hellow world”;
return p;
}
void Test(void)
{
char *str = NULL;
str = RetMemory();
printf(str);
}
由于p[]是自动变量,所以RetMenory执行完毕时,资源被回收,p指向未知地址。因此str的内容应是不可预测的, 打印的应该是str的地址
void getmemery(char *p)
{
p = (char *)malloc(100);
}
main()
{
char *str = NULL;
getmemery(str);
strcpy(str,"hello world!");
printf("%s\n",str);
} 段错误
问题出在这,上述代码传入getmemery(char *p)函数的字符串指针是形参,在函数内部修改形参并不能真正的改变传入形参的值,
执行完char *str = NULL; gememory(str);后的str仍为NULL;
一般函数的传递都是值传递,不会改变函数外的变量值。简单地说,就是形参不能够改变实参,实参只是复制了一份给形参!其自身并没有被改变
void getmemery(char **p)
{
*p = (char *)malloc(100);
}
main()
{
char *str = NULL;
getmemery(&str);
strcpy(*str,"hello world!");
printf("%s\n",*str);
}
这就是我们常说的“地址传递”,将str的地址传给getmemery()函数,getmemery()函数就会通过地址修改str里面的值,这样就会得到正确的结果。
所以,我们要记住函数传参的两种方式:1)值传递 2)地址传递。
下面两种if语句判断方式。请问哪种写法更好?为什么?
int n;
if (n == 10) // 第一种判断方式
if (10 == n) // 第二种判断方式
【答案】这是一个风格问题,第二种方式如果少了个=号,编译时就会报错,减少了出错的可能行,可以检测出是否少了=。
#include "stdafx.h"
#include
using namespace std;
class human {
public:
~human() {
//析构函数,该类对象在生存期结束的时候会自动调用的一个函数
cout << "human over......" << endl;
}
void Disp() {
cout << "human disp......" << endl;
}
};
class man : public human
{
public:
~man() {
cout << "man over......" << endl;
}
void Disp() {
cout << "man disp......" << endl;
}
};
int main()
{
human* p = new man; //定义和初始化指向一个类的指针变量
p->Disp(); //指向成员运算符,得到一个指针指向的元素
delete p;
system("pause");
return 0;
}
答案】
human disp…
human over…
以下三条输出语句分别输出什么?
char str1[] = “abc”;
char str2[] = “abc”;
const char str3[] = “abc”;
const char str4[] = “abc”;
const char* str5 = “abc”;
const char* str6 = “abc”;
cout << boolalpha << ( str1 == str2 ) << endl; // 输出什么?
cout << boolalpha << ( str3 == str4 ) << endl; // 输出什么?
cout << boolalpha << ( str5 == str6 ) << endl; // 输出什么?
【答案】分别输出false,false,true。str1和str2都是字符数组,每个都有其自己的存储区,
它们的值则是各存储区首地址,不等;str3和str4同上,只是按const语义,它们所指向的数据区不能修改。
str5和str6并非数组而是字符指针,并不分配存储区,其后的“abc”以常量形式存于静态数据区,而它们自己仅是指向该区首地址的指针,相等。
int a=5, b=7, c;
c = a+++b;
【答案】a=6,b=7,c=12
一个栈的入栈序列是A,B,C,D,E,则栈的不可能的输出序列是( )
A、EDCBA;
B、DECBA;
C、DCEAB;
D、ABCDE
【答案】C