C/C++ 面试大纲

C

程序运行

预处理: 展开头文件 替换宏定义的内容 去注释 条件编译
gcc -E cc.c -o cc.i

编译: 将c程序的指令和数据分开,分别映射到不同的程序段,生成汇编代码
gcc -S cc.i -o cc.s

汇编:将汇编代码转化为机器文件(二进制),为每一个源文件生而成一个
目标文件
gcc -c -o cc.s -o cc.o

链接: 将多个目标文件以及所需要的库文件链接成最终的可执行性文件
gcc cc.o - o hello

运行:
./hello

简易编译:
gcc cc.c -o hello
./hello

数组

arr 即代表构造类型,也代表首元素的地址
arr = &arr[0]
sizeof(arr) 整个数组的大小

int arr[10];
sizeof(arr): 40
sizeof(int[10]):40

  • 降维 实现: * arr = arr[0]
    &升维 实现 :&arr[0] = arr

a[i] == *(a+i)
a[i][j] == ((a+i)+j)

指针

指针大小
32 位机的情况下,无论是什么类 型大小均是 4。而 64 位机大小均是 8。这是由当前机型的地址总线决定的

指针常量,有类型的地址常量
char a = 1 ;
printf(“&a = %p\n”, &a);
printf(“a = %d\n”, ((char)0x0060FEAF));

指针变量,用以存放指针的量, 我们叫作,指针变量
定义:type * variable;

int *p = NULL;
printf(“%p\n”,p); // 0000000000000000
//printf(“%d\n”,p); //挂掉
printf(“%p\n”,&p); //000000000061FE18
printf(“%d\n”,
((int *)0x000000000061FE18)); // 0

初始化以及间接访问:
char p = (char)0x123456; //0x123456 是数值,(char*)0x123456 是指针
*p = ‘a’; // error

所以通常作法是,把一个己经开辟空间的变量的地址赋给指针变量。

int main()
{
int a = 100; // 变量a已开辟空间,main函数未结束
int *pa = &a;
printf(“*pa = %d\n”,*pa);
}

指向关系:
谁指向了谁,即保存了谁的地址。

野指针
一个指针变量,如果,指向一段无效的空间,则该指针称为野指针,常见情型有两种,一种是未初化的指针,一种 是指向一种己经被释放的空间。

如何避免野指针,初始化为null
define NULL ((void *)0)
既读不出东西,也不写进东西去

void 即无类型(非没有返回值),可以赋给任意类型的指针,本质即代表内存的最小单位,在 32 位机上地位等同于 char。

指针运算
种数值加类型运算
int a = 0x0001;
printf(“a = %#x a+1 = %#x\n”,a,a+1); //0x1 0x2
int p = (int)0x0001;
printf(“p= %#x p+1 = %#x\n”,p,p+1); // 0x1 0x5
只有当指针指向一串连续的存储单元时,指针的移动才有意义。

访问:
数组名代表数组首元素的地址,可以用下标法和本质法进行访问,指针法访问(冲锋枪)
int array[10] = {1,2,3,4,5,6,7,8,9,0};
for(int i=0; i<10; i++)
{
printf(“%d\n”,array[i]);
}
for(int i=0; i<10; i++)
{
printf(“%d\n”,*(array+i));
}
int *p = array;
printf(“%d\n”,*p++); // p++:等价于(p++)
printf(“%d\n”,(*p)++); // 1 2 3 4 5 6 7 8 9 10 error

二维数组的访问:
int arr[3][4] = {1,2,3,4,10,20,30,40,100,200,300,400};

printf(“%d\n”,arr[1][1]);
printf(“%d\n”,(arr[1]+1));
printf(“%d\n”,
(*(arr+1)+1)); //行,列

//arr[i] 第 i 行,首元素的地址 一级指针
//arr+i 第 i 行的地址 数组指针

arr[0] 值为地址
printf(“%d\n”,*arr[0]);

arr 是数组首元素的地址,所以 arr 的值和 &arr[0]的值相同,
另一方面,arr[0]本身是包含4 个整数的数组,
因此,arr[0]的值同其首元素的地址&arr[0][0]相同

字符串

char *p = “china”; // china 不可以需修改
char arr[] = “china”; // china 可以修改
printf(“%s\n”,p);
printf(“%s\n”,arr);

“china” 存储在rodata段,不可以修改, 指针不可以修改,拷贝到字符指针数组中,可以修该

char *p = “china”;
char arr[10] = “china”;

strlen(“china”) : 5
sizeof(“china”): 6

strlen( p) :5
sizeof( p): 4

strlen(arr): 5
sizeof(arr):10

字符串操作函数
1 求长 strlen
int myStrlen(char *dest)
{
   
    int count=0;
    while(*dest++)
        count++;
    return count;
}

2 链接 strcat

char *myStrcat(char * dest,char *src)
{
   
    char * d = dest;
    while(*dest) dest++;
    while(*dest++ = *src++);
    return d;
}


3 拷贝 strcpy

char * myStrcpy(char * dest, char * src)
{
   
    char * d = dest;
    while(*dest++ = *src++);
    return d;
}

4 比较 strcmp

int mystrCmp(char * s1, char *s2)
{
   
   for(;*s1 && *s2;s1++,s2++)
   {
   
      if (*s1 != *s2)
          break;
   }
   return *s1 - *s2;
}
内存模型

命令行参数/环境变量
stack
heap
数据段: 未初始化(bss)、 初始化(rw)
代码段

void * malloc(size_t size);
int * pa = (int *) malloc(10 *sizeof(int));
memset(pa,0,10 * sizeof(int));

int * array = (int *)calloc (10, sizeof(int));

realloc :扩容 第一个参数未待扩容的指针, 第二个参数为扩容后内存的大小
char * pa = (char *) malloc(10);
pa = realloc(pa,30);

内存对齐

内存不对齐: 一个成员变量需要多个机器周期去读
对齐规则:
1 取pack(n) 的值 , 取结构体中类型最大的值, 两者取小作为外对齐的值
2 将外对齐的值 每个结构体成员比较,取小 ,作为内对齐的值
3 设起始地址为0,若地址可以整除 第二步内对齐的值,存放数据,若不整除,找到整除的地址
4 最后进行补空,直到可以整除第一步外对齐的值, 即地址大小为结构体大小

结构体成员,从第一个变量开始,放在结构体的开始地址处

数据的低位字节:最右侧 ; 高位地址:最左侧
大端存储:数据的低位字节放到高地址,高地址放到低地址处

struct stu {
char age;
int num;
}
地址: age < num

内存泄露

使用: 申请 判空 使用 释放 制空
malloc 和 free 配对 使用
malloc 多余 free ,内存泄露, free 多于malloc , 造成double free
谁申请谁释放

检测内存泄露:
1 日志分析
2 单元测试
3 分析工具heob

内存拷贝

strcpy: 实现字符串的拷贝,遇到 /0结束
sprintf: 格式化字符串
memcpy: 实现内存块的拷贝,根据size大小赋值

void memcpy(void * destin, void * sourcre, unsiged n);
以source为起始地址拷贝destin n 个字节

memcpy最快,strcpy次之,sprintf最慢

链表

内存模型:理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接的
应用场景:
动态数据结构,需要频繁插入和删除元素的数据结构
内存管理:实现自定义的内存管理器,动态分配和释放内存块
深度优先搜索:在图形遍历算法中,使用链表来表示节点的邻接关系

头插法:

//头插法  让新来的节点有所指向
void insertHeadList(Node* head,int data) {
   
    Node * cur = (Node *)malloc(sizeof(Node));
    cur->data = data;

    cur->next = head->next;
    head->next = cur;
}

尾插法:

void insertTailList(Node * head, int data) {
   

    Node *cur = (Node *)malloc(sizeof(Node));
    cur->data= data;

    while(head->next) {
   
        head = head->next;
    }
    head->next = cur;
    cur->next = NULL;

}
文件

c标准IO库: fopen 从内存中去读数据
系统函数: open 从用户态切换到内核态,在从内核态切换到用户态

FILE * fopen ( const char * filename, const char * mode );

FILE* fp = fopen(“ascii2.txt”, “w”);
在这里插入图片描述
int fclose ( FILE * stream );
int fputc (int ch, FILE * stream );
int fgetc ( FILE * stream );

int feof( FILE * stream ) 判断文件是否读到文件结尾

int fputs(char *str,FILE *fp) 把 str 指向的字符串写入 fp 指向的文件中。
char *fgets(char *str,int length,FILE *fp) 从 fp 所指向的文件中,至多读 length-1 个字符,送入字符数组 str

int fwrite(void *buffer, int num_bytes, int count, FILE *fp)
把 buffer 指向的数据写入fp 指向的文件中

int fread(void *buffer, int num_bytes, int count, FILE *fp)
fp 指向的文件中的数据读到 buffer 中

char rbuf[1024];
int n;
n = fread(rbuf,1,1024,pf);
printf(“n = %d\n”,n); // n =6

n = fread(rbuf,1024,1,pf);
printf(“n = %d\n”,n); // n =0

void rewind ( FILE * stream ); // 将文件指针重新指向一个流的开头
int fseek ( FILE * stream, long offset, int where) // 文件偏移, offset 偏移量 where 起始偏移的位置

排序

快排

1 先从数列中取出一个数作为基准数(通常取第一个数)。
2 分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它
的左边。
3 再对左右区间重复第二步,直到各区间只有一个数

选定基石,左右交替
右侧:大而移动,小而赋值
左侧:小而移动,大而赋值
最后确定基石位置,如法炮制

优点:极快,移动数据少
缺点: 不稳定和递归
时间复杂度: nlogn

void quickSort(int *p, int left, int right)
{
   
    if(left < right)
    {
   
        int pivot = p[left];
        int low = left; int high = right;
        while(low<high)
        {
   
            while(p[high]>=pivot && low<high)
                high--;
            p[low] = p[high];
            while(p[low] <=pivot && low<high)
                low++;
            p[high] = p[low];
        }
        p[low] = pivot;
        quickSort(p,left,low-1);
        quickSort(p,low+1,right);
    }
}
选择

第一趟第一个元素和后面的元素依次比较,比较n-1趟
时间复杂度: n2

for(int i=0;i<n-1;i++)
{
   
	for(int j=i+1;j<n-1;j++)
	{
   
		if(arr[i]  > arr[j])
		{
   
		     arr[i] ^= arr[j];
			 arr[j] ^= arr[i];
			 arr[i] ^= arr[j];
		}
	}
}

冒泡

第一趟第一个元素和第二元素比较,若升序,大于则交换,比较n-1趟

优点:稳定
时间复杂度: n2

for(int i=0;i<n-1;i++)
{
   
	for(int j=0;j<n-1-i;j++)
	{
   
		int count = 0;
		if(arr[j]>= arr[j+1])
		{
   
		
			count = 1;
		}
	}
	if(count == 0)
		break;
}

折半
int left =0, right = n-1, midIdx;
int idx = -1;
while(left <= right)
{
   
	midIdx = (left + right)/2;
	if(findData == arr[midIdx])
	{
   
		idx == midIdx;
	}
	else if (findData >= arr[midIdx])
	{
   
		left = midIdx +1;
	}
	else if (findData <= arr[midIdx])
	{
   
		right = midIdx -1;
	}
}
if(idx != -1)
	printf("%d\n",idx);
else
	printf("find none");

C++

封装

封装,可以达到,对外提供接口,屏蔽数据,对内开放数据
统一接口:封装可以将类的内部实现和外部接口分离,从而简化了类的使用方式,使得用户只需要关注如何使用类的接口而不需要关心具体实现。

继承

继承是指一个类可以从另一个类中继承属性和方法,继承是一种"is-a"关系
聚合是指一个对象可以由多个其他对象组成,每个组成对象都可以独立存在,聚合是一种"has-a"关系

如果一个类需要拥有另一个类的所有属性和方法,并且可以在此基础上增加新的属性和方法,则可以选择继承关系;如果一个类需要由多个其他对象组成,则可以选择聚合关系。

多态

多态,允许子类对象能够以自己的方式实现从父类继承的方法,如

实现多态类型的容器:我们可以定义一个父类指针类型的容器,将多个不同子类对象放入其中,这样在程序运行时就可以根据实际情况选择调用哪个子类对象的方法

实现策略模式:策略模式是指定义一系列算法,将每个算法都封装起来,并且使它们之间可以互换。这种情况下就可以通过定义一个父类指针类型的指针来实现多态性,让程序在运行时动态地选择使用哪种算法。

实现模板方法:模板方法是指将具体实现交给子类来完成,而在父类中定义算法的框架。这种情况下就可以通过定义一个父类指针类型的指针来实现多态性,让子类来实现具体的算法

类型转化:

static_cast: 基本类型转化
dynamic_cast: 多态类型转化,基类指针转化为派生类
reinterpret_cast : 不同指针类型转化
const_cast : 去 const 属性

默认构造

class A {}

  • 无参构造
    A() {}

  • 拷贝构造
    A(const A & another)() {}

  • 赋值运算符号重载
    A & operator = (const A & another) {}

  • 析构
    ~A()

继承方式

在这里插入图片描述
全盘接收,除了构造器与析构器。基类有可能会造成派生类的成员冗余,所以说基类是需设计的

构造顺序

派生类名::派生类名(参数总表)
:基类名(参数表),内嵌子对象(参数表)
{
派生类新增成员的初始化语句; //也可出现地参数列表中
}
先是基类的构造顺序,然后是内嵌子对象的构造顺序,之后是派生类的构造顺序,析构顺序与之相反

Student(string name)
:_name(name){
   }

Birthday(int y)
:_year(y)

class Graduate:public Student {
   
private:
	Birthday birth;
}


Graduate(string name,float salary,int y)
:Student(name),_salary(salary),birth(y)


虚继承

目的:解决C++中多重继承的问题,子类继承不同父类中相同的成员,存在多次拷贝,1 放浪费存储空间 2 存在二义性

实现方法:
1 提取公共成员组成祖父类
2 各父类虚继承虚基类

多态:

静多态:
函数重载的条件:

  • 函数名相同
  • 参数个数不同,参数的类型不同,参数顺序不同
  • 返回值类型,不作为重载的标准

动多态:
父类中由虚函数
子类覆写父类中的虚函数
子类对象赋值给父类指针,父类指针调用虚函数

纯虚函数: 虚函数被赋值给0

原理:
若类使用虚函数,则会为类生成虚函数表(一维数组,存放了虚函数表的地址),类对象构造时会初始化虚函数表的指针

虚析构函数

含有虚函数的类,析构函数也应该声明为虚函数
将基类的析构函数声明为virtual , 防止内存泄露

定义一个基类的指针p, 在delete p 时,当赋值p的对象是派生类的对象时
a 基类的析构函数是虚函数:
先调用派生类的析构函数,再调用基类的析构函数

b 基类的析构函数不是虚函数:
只会析构派生的析构函数, 基类的析构函数没有调用 (根据指针的类型进行性判断,不会去赋值的对象),造成内存泄露

~Parent()
{
cout << "调用父类 Parent 析构函数 " << endl;
}
~Child()
{
cout << "调用子类 Child 析构函数 " << endl;
}
Parent* parent = new Child();

调用父类 Parent 析构函数


virtual ~Parent()
{
cout << "调用父类 Parent 析构函数 " << endl;
}
virtual ~Child()
{
cout << "调用子类 Child 析构函数 " << endl;
}
Parent* parent = new Child();

调用父类 Parent 析构函数
调用子类 Parent 析构函数

虚构造

从存储空间角度,虚函数对应一个虚函数表,如果将构造函数设置为虚函数,需要到虚函数表中调用,但是对象没有实例化,没有分配内存空间,则不能调用

C++11

lambda

匿名函数
{}
[](int x,int y){return x > y;}
sort(vi.begin(),vi.end(),[](int x,int y){return x > y;});

functor

重载了 operator()的类的对象,在使用中,语法类型于函数。故称其为仿函数
int add(int i,int j)
{
return i + j;
}
auto op = function<int(int,int)>(add);
cout<<op(1,2)<<endl;

移动构造

将类A 对象a 移动赋值给 对象b, std::move() 能把左值强制转换为右值,它通过接收一个右值引用参数来创建新对象,进行移动资源而非进行深拷贝

使用场景
在函数中返回临时对象时,可以通过移动构造函数避免不必要的拷贝操作
在容器中插入临时对象时,可以通过移动构造函数实现高效插入和删除操作
在进行资源管理时,通过移动构造函数可以从一个对象转移资源所有权,提高性能

实现:
class Move
{
Move( Move && another)
{
cout<<" Move( Move && another)"<<endl;
_i = another._i;
another._i = nullptr;
}
int *_i;
}
int main() {
Move a;
Move b(std::move(a));
}
运行结果:
a的构造和析构,打印移动构造,Move( Move && another)

智能指针:

auto_ptr : 代理了被托管的对象指针,管理对象的生命周期

unique_ptr :不可以拷贝也不可以赋值;

shared_ptr: 解决了 auto_ptr 中引用同一个对象,在内部保持一个引用计数,并且仅当引用计数为 0 才能被删除,解决c中指针悬挂的问题,一个指针申请内存,多个指针指向这一块内容,当一个指针释放空间后,多个指针使用不安全

weak_ptr:用来解决shared_ptr循环引用,导致引用计数不能置0,资源无法释放问题

多线程

定义:
void func()
{
}
int main() {
thread t(func);
t.join(); // detach()
}

mutex锁:
lock():调用线程将锁住该互斥量
unlock():解锁,释放对互斥量的所有权
try_lock():尝试锁住互斥量, 如果当前互斥量被其他线程锁住,则当前调用线程返回 false

mutex mtx;
mtx.lock();

lock_guard : 解决死锁问题, 采用RAII思想(资源获取即初始化,在 lock_guard 对象构造时,传入的 Mutex 对象(即它所管理的 Mutex 对象会被当前线程锁住。在 lock_guard 对象被析构时,它所管理的 Mutex 对象会自动解锁。
lock_guard不能拷贝、赋值、移动,只能通过构造函数初始化和析构函数销毁

lock_unique: 采用RAII思想,相对于lock_guard 是可移动的,可以拷贝、赋值、移动;同时,可用于添加条件变量

条件变量:
利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待某个条件为真,而将自己挂起;另一个线程使的条件成立,并通知等待的线程继续,std::condition_variable 需要与 std::unique_lock 配合使用

api:
wait:当前线程调用 wait()后将被阻塞
notify_all: 唤醒所有的 wait 线程

STL

组成: 空间适配器 allocator , 容器 container, 算法 ,迭代器iterator
和仿函数组成

vector

特点:模板实现,变长数组,可以通过下标访问
底层: 动态数组
内存模型: 翻倍增长,申请新的内存空间,拷贝数据,释放旧的内存空间,底层实现为c库中的realloc()函数
api :
在这里插入图片描述
vector c(n) // n个元素
vector c(n,elem) // 初始化为elem
c[idx]
insert(pos,elem)
insert(pos,beg,end)
erase(pos)
erase(beg,end)
clear()

reserve() : 对vector预留内存空间,使用前并不申请空间
resize() : 当>size , 等同与push_back; 当<size,等同与pop_back()
size() : 容器实际大小
capacity() : reserve之后的大小
shrink_to_fit() :调整vector的capatity大小,与size相同

	vecor<int> coll = {
   1,3,5,6,7,2,4,6,8,10}&#x
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

八月的雨季997

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值