1 32位 计算机上char, int , long int 字节长度为
1,2,4
2 常量与变量区别
常量 | 变量 | |
初始化 | 需要 | 可不 |
寻址 | 不可,且地址不可赋给非常量指针 | 可 |
效率 | 以立即数形式(指在立即寻址方式指令中给出的数)编译,效率高 | 使用内存中的变量,低 |
左右值 | 只能右值 | 均可 |
const int a=0;
int aa=3;
int const * b=&a;
int *const k=&aa;
cout<<*b<<" "<< *k<<endl;
3 逻辑或操作,确定第一个操作为真,是否会计算后面的操作,&&呢?
逻辑或不会,&&会
4 全局变量与局部变量是否可以重名?
可以,但最好不要
5 变量的存储类型
按变量的生命周期,可以分为静态存储方式和动态存储方式
在C++中,可分为自动类型(auto)、静态存储变量(static)、寄存器类型变量(register)、外部类型变量(extern).
其中register变量告诉编译器相关的变量应该改量存储在高速寄存器中。使用register存储类型的目的一般是为了提高执行速度,但是,register声明只是向编译器所提出的“建议”,并非强制要求。
6 前置++与后置++
对于基础类型来说,效率基本相同,且二者都不是原子操作;
前置递增先对变量++,然后再计算表达式;
后置递增先计算表达式,然后再对变量++。
而对于类而言,在重载++运算符时,前置++的效率高于后置++,原因在于前置操作符没有构造函数。
i++ 不能作为左值,而++i 可以:
如下面代码所示
int i = 0;
int *p1 = &(++i);//正确
int *p2 = &(i++);//错误
++i = 1;//正确
i++ = 1;//错误
#include <iostream>
#include <string>
using namespace std;
class plus2Test{
int mValue;
public:
plus2Test(int i):mValue(i)
{ }
inline int get_value()
{ return mValue;}
plus2Test& operator ++()
{
++mValue;
}
plus2Test operator ++(int)
{
plus2Test ret(mValue);
//当前对象的值保存在一个临时对象里,或者这样写plus2Test ret=*this;
mValue++;
return ret;
}
};
int main()
{
plus2Test t(0);
++t;
cout<<t.get_value()<<endl;
cout<<(t++).get_value()<<endl;
return 0;
}
输出
1
1
(182条消息) C+±-前置操作符与后置操作符_cangnuan2978的博客-优快云博客
[C++再学习系列] 前置++与后置++ - zhenjing - 博客园 (cnblogs.com)
所以在32位计算机中,一个指针变量的返回值必定是4(注意结果是以字节为单位),但是,在64位系统中指针变量的sizeof结果为8。
7 什么是引用
C++对象的另一个名字,是一种复合类型,由其他类型定义的类型,本质是一个指针常量
data_type *const x=&object;
8 如何使用const引用和非const的引用
const引用可以读取但不可修改引用对象,被引用也必须const对象,
参考视频
9 volatile作用,使用场合
与const绝对对⽴的,是类型修饰符]影响编译器编译的结果,⽤该关键字声明的变量表示该变量随时可能发⽣变化,与该变量有关的运算,不要进⾏编译优化;会从内存中重新装载内容,⽽不是直接从寄存器拷⻉内容。
作用:指令关键字,确保本条指令不会因编译器的优化⽽省略,且要求每次直接读值,保证对特殊地址的稳定访问
使用场合:在中断服务程序和cpu相关寄存器的定义
10 C++强制类型转换
static_cast, dynamic_cast,reinterpret_cast 和const_cast
- static_cast 静态转换 用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换 - 进行上行转换(把派生类的指针或引用转换成基类表示)是安全的 - 进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的 用于基本数据类型之间的转换,如把 int 转换成 char,把 char 转换成 int。这种转换的安全性也要开发人员来保证
- dynamic_cast 动态转换 dynamic_cast 主要用于类层次间的上行转换和下行转换 在类层次间进行上行转换时,dynamic_cast 和 static_cast 的效果是一样的 在进行下行转换时,dynamic_cast 具有类型检查的功能,比 static_cast 更安全
- const_cast 常量转换 该运算符用来修改类型的const属性 常量指针被转化成非常量指针,并且仍然指向原来的对象 常量引用被转换成非常量引用,并且仍然指向原来的对象 注意:不能直接对非指针和非引用的变量使用 const_cast 操作符
- reinterpret_cast 重新解释转换 这是最不安全的一种转换机制,最有可能出问题 主要用于将一种数据类型从一种类型转换为另一种类型,它可以将一个指针转换成一个整数,也可以将一个整数转换成一个指针
static_cast
任何具有明确定义的类型转换,只要不包含**底层const(int const* p),**都可以使用static_cast。
但没有运行类型检查来确保转换的安全
进⾏上⾏转换(把派⽣类的指针或引⽤转换成基类表示)是安全的
进⾏下⾏转换(把基类的指针或引⽤转换为派⽣类表示),由于没有动态类型检查,所以是不安全的
const char *cp;
// char *p=static_cast<char*>(cp);//error static_cast 无法丢掉常量或其他类型限定符
const_cast
T:常量指针转换为非常量指针,并且仍然指向原来的对象;
常量引用转换非常量引用,也指向原来对象.
Y:去掉类型的const属性和volatile属性
char *q=const_cast<char*>(cp);
// const_cast<string>(cp);//error const_cast只改变常量属性
reinterpret_cast
通常为运算对象的位模式提供较低层次上的重新解释.
可以将整形转换为指针,也可以把指针转换为数组,可以在指针和引用中进行转换。但非常危险
int *go;
char *pg=reinterpret_cast<char*>(go);
// cout<<*pg<<endl;
// string str(pg);pg指向的对象时一个int而非字符
dynamic_cast
在进行上下行转换时,具有类型检查,比static_cast更安全,但转换后**必须是类的指针、引用或者void*,**基类要有虚函数,可以交叉转换。用于类继承层次间的指针或引用转换
class p{
public:
p(){};
// virtual void s()=0;
virtual void show(){ cout<<" p class";}
};
class c:public p{
public:
c(){};
// void s()
// {
// cout<<"gogo"<<endl;
// }
void show(){ cout<<" c class"<<endl;}
};
int main()
{
p* base=new c;
if(c *der=dynamic_cast<c*>(base))//基类指针所指对象是派生类类型的,这种转换是安全的;
der->show();
else
cout<<"failure"<<endl;
// 另一种是基类指针所指对象为基类类型,不安全
return 0;
}
输出
c class
C++强制类型转换操作符 dynamic_cast - 狂奔~ - 博客园 (cnblogs.com)
11 指针与引用的区别
引用本质为指针常量,也就是type* const name;
指针 | 引用 | |
是否必须初始化 | 否 | 是 |
指向可否改变 | 可 | 不可 |
是否分配内存区域 | 是 | 否(引用就是变量内存的别名) |
sizeof()大小 | 变量地址大小 | 变量大小 |
是否可以为空 | 是 | 否 |
12 静态库和动态库怎么制作及如何使用,区别是什么。
静态库:
gcc xx.c -c//生成xx.o目标文件
ar rcs libxx.a xx.o //生成static lib libhello.a
使用
gcc x.c -lxx -o x //lib前缀 不要,.a不要
-l表示链接
动态库
gcc -shared -fpic xx.c -o libxx.so
-shared 指定生成动态库
-fpc fPIC选项作用于编译阶段,在生成目标文件时使用该选项,
以**生成位置无关的代码**。
使用
gcc x.c -lxx -L ./ -o x
-lxx:同静态库原理
-L:是指告诉gcc编译器先从-L指定的路径去找静态库,
默认是从/usr/lib/ 或者 /usr/local/lib/ 去找。
./: 当前路径
静态库 | 动态库 | |
装载速度 | 快(执行快) | 慢 |
内存大小 | 大 | 小 |
加载时机 | 编译 | 运行 |
后缀Linux | .a | .so |
windows | .lib | .dll |
13 对于大型数据(如数组,字符串和结构),为何需要使用new来分配
- 提高灵活性,因为new申请的是堆的内存,而堆的内存的生命周期是由调用者控制的(结束直接delete),并且可以自己控制内存的大小,尤其是对于大型数据而言这点很重要。
//一般创建数组的方式要求中括号里面是常量
int n;
cin>>n;
int arr[n];//在一些编译器中可能通不过,在编译阶段确定的,可以使用vector<int>
但使用new创建动态数组则不会
//typeName *pointer_name=new typeName[num_elements];
int num=10;
int *p=new int[num];//在程序阶段确定num的值
*p=10;
p[2]=2;//*(p+2)=2
cout<<p[0]<<endl;//10
cout<<p[1]<<endl;//此时的值是不确定的
cout<<p[2]<<endl;//输出2
delete [] p;//这里指针变量的名称,[]表示不仅释放首地址,而是整块数组的内存
//对空指针应用delete是安全的
- 就能够使用的最大内存,堆栈区<全局区(静态区)<堆区<内存映射文件<磁盘<云存储,为了能够应对大型数据的存储需求,需要使用new申请堆区内存。
(182条消息) C\C++内存区域划分比较_QbaixueQ的博客-优快云博客
(182条消息) C++基础语法梳理:inline 内联函数!虚函数可以是内联函数吗?_一起学编程的博客-优快云博客
14 include头文件的顺序以及双引号""和尖括号<>的区别
""的头文件是自定义文件,<>的头文件是系统文件;
编译预处理时<>的头文件的查找路径是编辑器设置的头文件路径→系统变量。而使用""的是当前头文件目录→编译器设置的头文件路径→系统变量。
15 导入C函数的关键字是什么,C++编译时和C有什么不同?
extern “C”
编译时C++在编译函数时会将函数和函数的参数列表类型也加到编译后的代码中,而C中一般只包含函数名。原因在于C++支持函数重载。对于C++数据变量有嵌套(在名称空间或类里)也会按照响应规则来处理,
extern "C"
{
#include<string.h>
void printfM(string str)//在C++程序里边声明该函数,会指示编译器这部分代码按C语言的进行编译
{
printf("%s\n",str.c_str());//需要使用c_str()进行转换
}
}
void testC(){
printfM("xx");
}
在头文件中通过条件编译引入 extern “C”。
#ifdef __cplusplus
extern "C" {
#endif
int add(int, int);
#ifdef __cplusplus
}
#endif
__cplusplus
是cpp中的自定义宏,那么定义了这个宏的话表示这是一段cpp的代码,也就是说,上面的代码的含义是:如果这是一段cpp的代码,那么加入extern “C”{和}处理其中的代码。
(185条消息) extern “C“的作用及理解_米碎师兄的博客-优快云博客_extern “c”
(185条消息) 用printf输出string类型数据总结_胡涂胡话的博客-优快云博客_打印string的内容
为什么需要 extern “C” ? - 知乎 (zhihu.com)
16 .hpp 文件
T
将.cpp的实现代码混入.h头文件当中,定义与实现都包含在同一文件,则该类的调用者只需要include该.hpp文件即可,无需再将cpp加入到project中进行编译。而实现代码将直接编译到调用者的obj文件中,不再生成单独的obj,采用hpp将大幅度减少调用project中的cpp文件数与编译次数,也不用再发布lib与dll文件,因此非常适合用来编写公用的开源库。如超级难用的Eigen库也有这种方式的实现
W
1、是Header Plus Plus的简写。(.h和.hpp就如同.c和.cpp似的)
2、与.h类似,.hpp是C++程序头文件格式。
3、是VCL专用的头文件,已预编译。
4、是一般模板类的头文件。
5、一般来说,.h里面只有声明,没有实现,而.hpp里声明实现都有,后者可以减少.cpp的数量。
6、.h里面可以有using namespace std,而.hpp里则无。
7、不可包含全局对象和全局函数。
8, 类之间不可循环调用
9、不可使用静态成员
7的原因在于
.hpp本质上是作为.h被调用者include的,所以当hpp文件中存在全局对象或者全局函数,而该hpp被多个调用者include时,将在链接时导致符号重定义错误。要避免这种情况,需要去除全局对象,将全局函数封装为类的静态方法
全局类对象
一个类让全部的CPP都能用
假设有一个预编译头stdafx.h
在stdafx.h中加入你想要的全局类的头文件: include “CMyClass.h” ;
在stdafx.cpp中加入类的定义 如:CMyClass myclass;
在你想要用到该全局CMyClass类的其他类CTestProjectApp.h的头文件中: include “stdafx.h”,并在CTestProjectApp.h类的头文件的开头处(class 声明的前面)加上 extern CMyClass Myclass;
记得初始化。
比如有如下代码:
step1: 预编译头stdafx.h 或者单独的其他头文件:
#include <iostream>
#include "CMyClass.h"
step2: stdafx.cpp
#include "stdafx.h"
CMyClass myclass;
step3: 使用全局类的其他类CTestProjectApp.h:
#include "stdafx.h"
#include "CTestProjectApp.h"
extern CMyClass Myclass;
class CTestProjectApp
{
....
void test();
}
step4: 记得在CTestProjectApp.cpp中初始化该全局类:
#include "CTestProjectApp.h"
void CTestProjectApp::test()
{
Myclass = new CMyClass();
}
17 Gcc与g++区别
gcc和g++都是GNU(组织)的一个编译器。
误区一:gcc只能编译c代码,g++只能编译c++代码 两者都可以,但是请注意:
1.后缀为.c的,gcc把它当作是C程序,而 g++当作是c++程序;后缀为.cpp的,两者都会认为是c++程序,注意,虽然c++是c的超集,但是两者对语法的要求是有区别的。C++的语法规则更加严谨一些。
2.编译阶段,g++会调用gcc,对于c++代码,两者是等价的,但是因为gcc命令不能自动和C++程序使用的库联接,所以通常用g++来完成链接,为了统一起见,干脆编译/链接统统用g++了,这就给人一种错觉,好像cpp程序只能用g++似的。
误区二:gcc不会定义__cplusplus宏,而g++会 实际上,这个宏只是标志着编译器将会把代码按C还是C++语法来解释,如上所述,如果后缀为.c,并且采用gcc编译器,则该宏就是未定义的,否则,就是已定义。
误区三:编译只能用gcc,链接只能用g++ 严格来说,这句话不算错误,但是它混淆了概念,应该这样说:**编译可以用gcc/g++,而链接可以用g++或者gcc -lstdc++。**因为gcc命令不能自动和C++程序使用的库联接,所以通常使用g++来完成联接。但在编译阶段,g++会自动调用gcc,二者等价。
18 define 和 inline 的区别
define是定义预编译时处理的宏,作为字符串的替换,不分配内存,无类型检查,不安全
而inline是将内联函数编译完成生成的函数体直接插入到被调用的地方,省去了压栈,跳转和返回操作。也就没有了函数调用额外的开销。
inline不能包含复杂的控制语句,while Switch。
内联函数会进行类型检查,有编译限制,也就是不合理编译器能够拒绝请求,使用更安全
//宏定义示例
#define MAX(a,b) ((a)>(b)?(a):(b))
MAX(a,"Hello"); //错误地比较int和字符串,没有参数类型检查
//内联函数示例
#include <stdio.h>
inline int add(int a, int b){
return (a + b);
}
int main(void){
int a;
a = add(1, 2);
printf("a+b=%d\n", a);
return 0;
}
//以上a = add(1, 2);处在编译时将被展开为:a = (a + b);
19 new 和 malloc的区别与联系
new | malloc | |
属性 | 运算符 | 标准库函数 |
内存大小分配 | 自动计算 | 手工计算 |
返回值 | 成功:指定类型的指针 失败:bac_alloc异常 | 成功:指向被分配内存的指针, 失败:空指针NULL |
返回类型安全性 | 安全 | 不安全 (需要对void*指针强制转换为我们需要的类型) |
是否调用构造函数 | 是 | 否 |
关系 | new的实现是通过对malloc的封装 |
#include <iostream>
using namespace std;
int* get_num()
{
int *a=new int(10);
return a;
}
int main()
{
int* res = get_num();
cout<<*res<<endl;
delete res;
return 0;
}
new 数据类型(初始值)
**malloc底层实现:**当开辟的空间小于 128K 时,调用 brk()函数;当开辟的空间大于 128K 时,调用mmap()。malloc采用的是内存池的管理方式,以减少内存碎片。先申请大块内存作为堆区,然后将堆区分为多个内存块。当用户申请内存时,直接从堆区分配一块合适的空闲快。采用隐式链表将所有空闲块,每一个空闲块记录了一个未分配的、连续的内存地址。
**new底层实现:**关键字new在调用构造函数的时候实际上进行了如下的几个步骤:
- 创建一个新的对象
- 将构造函数的作用域赋值给这个新的对象(因此this指向了这个新的对象)
- 执行构造函数中的代码**(为这个新对象添加属性)**
- 返回新对象
开辟数组
#include <iostream>
using namespace std;
int main()
{
// 开辟一维数组
int *a=new int[10];
for(int i = 0;i<10;i++)
a[i] = i;
for(int i = 0;i<10;i++)
cout<<a[i];
cout<<endl;
delete[] a;
// 开辟一维数组的同时赋值
int* a2 = new int[5]{1,2,3};
for(int i = 0;i<5;i++)
cout<<a2[i];
cout<<endl;
delete[] a2;
// 开辟二维数组
// int (*a3)[5]表示数组指针
int (*a3)[5]=new int[5][5];
int **a33=new int*[5];
for(int i = 0;i<5;i++)
{
for(int j = 0;j<5;j++)
a3[i][j] = 10;
}
cout<<*(a3[4]+4)<<endl;
delete[] a3;
// 开辟二维数组的同时赋值
// int (*a4)[5]表示数组指针
int (*a4)[5]=new int[5][5];
cout<<a4[1][2]<<endl;
delete[] a4;
}
delete 释放的是开辟的内存中的变量,开辟的内存没有被释放。
#include <iostream>
using namespace std;
int main()
{
int *a = new int(5);
delete a;
// a所指向的地址依旧存在,没有被释放
cout<<a<<endl;
// a所指向的地址中的值被释放,此时*a是一个垃圾数
cout<<*a<<endl;
*a = 100;
cout<<*a<<endl;
int *a=new int[10];
delete [] a;//释放int数组空间
}
malloc
原型:extern void *malloc(unsigned int num_bytes);
功能:分配长度为num_bytes字节的内存块说明:如果分配成功则返回指向被分配内存的指针,否则返回空指针NULL。当内存不再使用时,应使用free()函数将内存块释放。
#include<stdio.h>
#include<malloc.h>
int main()
{
char *p;
p=(char *)malloc(10);
if(p)
printf("Memory Allocated at: %x/n",p);
else
printf("Not Enough Memory!/n");
free(p);
return 0;
}
使用allocator与new区别
allocator仅仅分配内存,而不自动完成参与对象的构造
allocator<char> str;
char* base = str.allocate(10), *p = base; //内存分配
str.construct(p++, 'a'); //对象构造并初始化
str.construct(p++, 'b');
cout << base[0] << base[1];
销毁
str.destroy(--p); //销毁对象
str.destroy(--p);
str.deallocate(base, 10); //释放内存
no discard 返回值不可丢弃
std::allocator - cppreference.com
20 sizeof是否返回动态分配的内存大小?
不会,sizeof确定内存大小是在编译处理完成的,而动态内存的大小是在运行时确定的
vector<int> vec;
vec.push_back(10);
cout<<sizeof(vec)<<endl;
21 static关键字的作用
关键字、作用域、生命周期、共享数据
static 是一个关键字,可以用来修饰局部变量、全局变量、成员变量、函数和成员方法。
主要作用有:限制数据的作用域、延长数据的生命周期、修饰成员可以被该类所有对象共享。
-
限制数据的作用域(隐藏) 所有没有加 static 的全局变量和函数都具有全局可见性,其它源文件中也可以访问。被 static 修饰的全局变量和函数只能在当前源文件中访问,其它源文件访问不了,利用这个特性可以在不同的文件中定义同名变量和同名函数,而不必担心命名冲突。
-
延长数据的生命周期 普通的局部变量出了作用域就会释放,而静态变量存储在静态区,知道程序运行结束才会释放。
-
静态成员被该类所有对象共享 static 关键字可以修饰类中的成员变量和成员方法,被称为静态成员变量和静态成员方法,静态成员拥有一块单独的存储区,不管创建多少个该类的对象,所有对象都共享这一块内存。静态成员本质上属于类,可以通过类名直接访问。
加分回答 1. 静态变量默认初始化值为0,如果没有显示初始化静态变量或者初始化为0的静态变量会存储在BSS段,而初显示初始化的静态变量存储在DATA段。 2. 静态成员函数中不能访问普通的成员变量,只能访问静态成员变量,并且在静态成员函数中没有 this 指针。
指针操作超出了变量的作用域。
初始化的时候一定要注意赋值。
销毁的时候一定要置为空。
22 静态变量,全局变量和局部变量的区别
对于变量的区分可以从内存位置,作用域。生命周期来进行区分。
位置
局部变量:在函数中或者方法中,函数的参数,局部代码块中。
全局变量:在文件中,函数外。
静态变量:使用 static 修饰,可以是局部、全局或者修饰类成员。
作用域
局部变量:作用域为局部,也就是函数或方法中,出了作用域就不能访问,同一作用域不能有同名的变量,如果全局变量和局部变量同名,则访问时采用"就近原则"。
全局变量:作用域为全局,在本文件或者其它文件中都可以访问,在其它文件中访问可以通过 extern 进行声明,表示使用外部的全部变量。
静态变量:静态局部变量作用域为局部,静态全局变量作用域为所在文件中,其它文件中访问不了。
内存位置
局部变量:存储在栈内存中。
全局变量:存储在静态存储区中,如果未初始化或者初始化为0,在BSS段,初始化了在DATA段。
静态变量:存储在静态存储区中,如果未初始化或者初始化为0,在BSS段,初始化了在DATA段。
BSS (全局区或静态区)
DATA 数据区
生命周期
局部变量:出了作用域销毁。
全局变量:程序结束销毁。
静态变量:程序结束销毁。
http://m.nowcoder.com/questions?uuid=f33242ae3f1f4dbe85573915ac974de2
23 const & static
修饰常量 | 修饰成员变量 | 修饰成员函数 | |
const | 定义时必须初始化,之后无法更改 超出作用域后被释放 | 不能在类外定义;只能通过构造函数的参数初始化列表初始化, 因为其只在某个对象的生命其是常量,而对于整个类是可变的(一个类可创建多个对象) | 目的是防止成员函数修改对象的内容,意味着不能修改成员变量的值,但可以访问。 const对象不可以调用非const的函数;但是非const对象可以 |
static | 在函数执行后不会释放其内存空间 | 只能在类内部定义声明,外部初始化(:: ),且不加static。它是类的一部分,所有对象的静态成员共享一块静态存储空间 | 目的是作为类作用域的全局函数。 不能访问和存取类的非静态成员变量;没有this指针;不能声明为虚函数 |
const和static不能同时修饰成员函数,原因在于静态成员函数不含有this指针,不能实例化,而const成员函数必须具体到某一实例
当调用一个对象的非静态成员函数时,系统会把该对象的起始地址赋给成员函数的this指针。而静态成员函数不属于任何一个对象,因此C++规定静态成员函数没有this指针(划重点,面试题常考)。既然它没有指向某一对象,也就无法对一个对象中的非静态成员进行访问。
T
#include<iostream>
#include<Windows.h>
using namespace std;
void staticF()
{
int num=0;
static int sc_num=0;//只初始化一次
// 如果你不加static 那么它存储在 栈中,即随着该函数的调用结束,栈中内容都会被弹出.因此程序结束,变量失效.
// 如果是static,他存储在全局数据区,不会因为你的函数调用结束而释放这个值.所以当你每次去修改它,
// 它的值都会被在这个位置记录下来,以后调用这个变量 依然会访问这个存储的地址.所以你的值能够不断被记录下来.
// "在函数多次调用中始终保持原来的值" 也就是这个意思
num++;
sc_num++;
cout<<"num="<<num<<endl;
cout<<"sc_num="<<sc_num<<endl;
}
class TestStatic{
public:
int n1=10;
// static int n2=2;//不能在类内初始化,带有类内初始值设定项的成员必须为常量
static int n2;
TestStatic(){n2++;}
int print_n2(){cout<<n2<<endl;return n2;}
// static int print_n1(){cout<<n1<<endl; return n1;} static 不能调用非静态成员变量
};
int TestStatic::n2=100;
//一定需要初始化,而且前面有类型符号,并且是公有的
int main()
{
for(int i=0;i<3;i++)
staticF();
TestStatic t1;
t1.print_n2();
TestStatic t2[5],t3;
t3.print_n2();
cout<<TestStatic::n2<<endl;//可以直接调用,而无需借助对象
return 0;
}
输出:
num=1
sc_num=1
num=1
sc_num=2
num=1
sc_num=3
101
107
107
Y
定义全局静态变量和局部静态变量;在变量前面加上static关键字。初始化的静态变量会在数据段分配内存,未初始化的静态变量会在BSS段分配内存。直到程序结束,静态变量始终会维持前值。只不过全局静态变量和局部静态变量的作用域不一样;静态变量智能在本文件中使用
定义静态函数:在函数返回类型前加上static关键字,函数即被定义为静态函数。静态函数只能在本源文件中使用;所以比较时采用这种类型的函数指针
24 程序启动过程
1.操作系统:
创建进程→分配空间;
加载器→可执行文件的数据段和代码段映射到进程的虚拟内存空间中
→读入可执行程序的导入符号表
→查找所有依赖的动态链接库。
→调用LoadLibrary
→查找对应的动态库文件,并为其确定一个合适的基地址
→加载器读取该动态链接库的导入符号表和导出符号表,比较应用程序要求的导入符号是否匹配该库的导出符号
→针对该库的导入符号表,查找对应的依赖的动态链接库。
→调用该动态链接库的初始化函数
→初始化应用程序的全局变量,对于全局对象自动调用构造函数
→进入应用程序入口点函数开始执行
25 字符串和字符数组的区别
字符数组,向内存申请的空间是数组里的存放个数+1,
char arr1[]={‘n’,‘a’,‘m’,‘e’};
字符串:
同样也是向内存申请的空间是数组里的存放个数+1,内存存放数组的字符元素+‘\0’,编译时编译器会自动在字符串的末尾添加此值,注意字符串是一个只读字符数组,不能够通过指针更改字符串内部数据
char arr2[]=“name”;
总结:c语言的字符串由C的字符数组末尾加上’\0’变形而成。
26 说说什么是函数指针,如何定义函数指针,有什么使用场景
T:函数指针就是指向函数的指针变量,每个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。
W:
int (*f)(int x,int y);//f是一个指针,指向一个函数
使用场景:call,调用他人提供的API函数;callback,他人的库调用我的函数。
27 静态变量什么时候初始化?
C中发生在编译的时候;而对于引入了对象的C++,全局或静态对象当且仅当对象首次用到时才进行构造。
静态全局变量,全局作用域,无法在其他文件使用。
静态局部变量,局部作用域,只能被初始化一次直到程序结束,
类中静态变量,当超出类作用域时就会回收。
28 nullptr调用成员函数可以吗?为什么?
能,
编译时对象就绑定了函数地址,无论指针是否空都可以
class Robot{
public:
void start()
{
cout<<"gogo"<<endl;
}
void stop()
{
cout<<"shut down"<<endl;
}
};
class car: public Robot{
public:
void start()
{
cout<<"car gogo"<<endl;
}
};
void testnullptr()
{
Robot *pr=nullptr;
pr->start();
car *c=nullptr;
c->start();
c->stop();
}
gogo
car gogo
shut down
29 static与类
- 定义全局静态变量和局部静态变量:在变量前面加上static关键字。初始化的静态变量会在数据段分配内存,未初始化的静态变量会在BSS段分配内存。直到程序结束,静态变量始终会维持前值。只不过全局静态变量和局部静态变量的作用域不一样;
- 定义静态函数:在函数返回类型前加上static关键字,函数即被定义为静态函数。静态函数只能在本源文件中使用;
- 在变量类型前加上static关键字,变量即被定义为静态变量。静态变量只能在本源文件中使用;
- 在c++中,static关键字可以用于定义类中的静态成员变量:使用静态数据成员,它既可以被当成全局变量那样去存储,但又被隐藏在类的内部。static 静态数据成员拥有一块单独的存储区,不管创建了多少个该类的对象,所有这些对象的静态数据成员共享这一块静态存储空间
- 也可以用于定义类中的静态成员函数,这是类的一部分,而非对象的一部分。
30 函数指针与指针函数
1、指针函数:指的就是返回值是指针的函数,本质就是个函数。int * xx( parameter list);
2、函数指针:指的是指向函数的指针变量,本质就是个指针。int (* xx)( parameter list);
区别的关键在于括号,括号运算符有限于*
W
int sum(int a,int b)
{
return a+b;
}
int sub(int a,int b)
{
return a-b;
}
int (*f)(int x,int y);//函数指针
int c;
int* sumPtr(int a,int b)// 指针函数
{
c=a+b;
return &c;
}
int* subPtr(int a,int b)
{
c=a-b;
return &c;
}
int * (*f2)(int x,int y);//指向指针的指针函数
void testFunctionPtr()
{
f=sum;
printf("a+b=%d\n",(*f)(1,2));
f=sub;
printf("a-b=%d\n",(*f)(1,2));
//指针函数
f2=subPtr;
printf("a-b=%d\n",*(*f2)(1,2));
}
a+b=3
a-b=-1
a-b=-1
sizeof
char str[] = "hello";
char* p = str;
int n = 10;
// 请计算
sizeof(str) = ?
sizeof(p) = ?
sizeof(n) = ?
void Func(char str[100])
{
// 请计算
sizeof(str) = ?
}
void* p = malloc(100);
// 请计算
sizeof(p) = ?
char str[] = "hello";
char* p = str;
int n = 10;
// 请计算
sizeof(str) = 6,数组计算时需要包含末尾的'\0'
sizeof(p) = 4, 32位系统占4个字节
sizeof(n) = 4,
void Func(char str[100])
{
// 请计算
sizeof(str) = 4, 数组首地址为指针的大小
}
void* p = malloc(100);
// 请计算
sizeof(p) = 4,p指向malloc分配的⼤⼩为100 byte的内存的起始地址
int a[]={1,2,3};
sizeof(a)=12, 4*3;
在《C陷阱和缺陷》中有两句话:
1.如果我们使用数组名作为函数参数,那么数组名会立刻转换为指向该数组第一个元素的指针。C语言会自动地将其作为参数的数组声明转换为相应的指针声明。
2.除了a被用作运算符sizeof的参数这一情形,在其他所有的情形中数组名a都代表指向数组a中下标为0的元素的指针。
31 说说const int a, int const *a, const int a, int *const a, const int *const a分别是什么,有什么特点
const int a
:a是一个常量,不允许修改
int const *a
:常量指针,指针指向内存的值不变=const int* a
int *const a
:指针常量,指向的地址不变,但值可变, 近似于引用
const int *const a
: 指针指向的地址和值都不变。
31 说说内联函数和函数的区别,内联函数的作用
内联函数有关键字inline声明
内联函数调用避免了压栈、跳转和返回操作,效率高
普通函数需要寻址,而内联函数不能包含复杂的结构控制语句
32 内联函数为何不能有复杂的结构
原因在于内联函数是通过牺牲内存来换取程序执行时间的缩短,省去了调用函数的过程,直接拷贝代码,如果函数体太大,这样编译成的指令比较冗余
不能出现复杂的结构控制语句例如while、switch、if、for,同时内联函数不能为递归。
这个其实是内联函数规定的,内联函数出现的目的就是为了节省函数执行时间(效率).
不是内联函数中不能有循环语句,而是当内联函数中出现了复杂的逻辑控制语句后,编译器会不再认为它是一个内联函数。
也就是说,当内联函数中实现过于复杂时,编译器会将它作为一个普通函数处理。
这是由内联函数的特殊性所决定的,由于内联是调用处展开的方式,所以编译器认为只有足够简单的函数才可以具有该特性,复杂函数编译器会放弃内联特性。
所以放心大胆地直接用inline