C/C++复习 day1



前言

随着C++课程的学习结束,接下来回顾一下C和C++中的难点,看看有什么是遗漏的。


一、C语言

1.memcpy函数

void* my_memcpy(void* des, const void* src, size_t num)
{
	assert(des != NULL && src != NULL);
	void* begin = des;
	for (size_t i = 0; i < num; i++)
	{
		*(char*)des = *(char*)src;
		des = (char*)des + 1;
		src = (char*)src + 1;
	}
	return begin;
}

按照字节数去拷贝赋值

2.memmove函数

// 从右往左移动则小到大赋值 从左往右复制,则从大到小赋值
void* my_memmove(void* des,const void* src,size_t num)
{
	assert(des!=NULL && src!=NULL);
	void* begin = des;
	if (des < src)
	{
		for (size_t i = 0; i < num; i++)
		{
			*(char*)des = *(char*)src;
			des = (char*)des + 1;
			src = (char*)src + 1;
		}
	}
	else
	{
		for (size_t i = num-1; i!=-1; i--)
		{
			*((char*)des + i) = *((char*)src + i);
		}
	}
	return begin;
}

注意赋值顺序

3.strstr函数

char* my_strstr(char* str1,const char* str2)
{
	int i = 0, j = 0;
	int flag = 0;
	char* begin = NULL;
	char* p1 = str1, *p2 = str2;
	while (p1[i] != '\0' && p2[j] != '\0')
	{
		if (p1[i] == p2[j])
		{
			if (flag == 0)
			{
				begin = p1+i;
				flag = 1;
			}
			i++;
			j++;
		}
		else
		{
			i = i - j + 1;
			j = 0;
			flag = 0;
		}
	}
	if (p2[j] == '\0')
	{
		return begin;
	}
	else
	{
		return NULL;
	}
}

4.宏定义的函数

#define ADD(x,y) ((x)+(y))

5.大小端的介绍以及判断

大小端

int main()
{
	// 判断大小端 01 00 00 00 01
	// 大端 01 02 03 04
	//      00 00 00 01
	// 小端 01 02 03 04
	//      01 00 00 00
	int i = 1;
	if (*(char*)&i == 1)
	{
		printf("小端\n");
	}
	else if (*(char*)(&i) == 0)
	{
		printf("大端\n");
	}
}

二、C++入门基础

1.C++是如何支持函数重载的?

函数重载
C++支持函数重载而C语言不支持是因为函数在内存中的存储方式不相同,C语言是直接以函数名修饰,而C++是_Z 函数名长度 函数名 类型首字母,导致C++支持重载,而C语言不支持重载。

2.建议用const enum inline去替代宏

三、C++类和对象

1.类大小的计算

类大小的计算
总结:1.类大小的计算不包括static修饰的静态的一系列东西,函数也不算在内。
2. 类大小的计算要符合结构体的对齐规则。
3. 空类的大小为1。
4. 虚函数(多态,重写):要存放一个虚函数表,先存该父类的虚函数表,再存父类中的成员变量。
5. 虚继承,父类中要多存放一个偏移量。

2.移动构造和移动赋值

说起这两个,就必须先提起C++11引入的右值引用和几个关键字。

1.右值引用

int&& rr1=10;
double&& rr2 = x+y;

之前的一个&表示对变量的引用,现在两个&&表示对右值的引用。

2.move关键字

比如我们正常这样写,编译器会报错不支持。
无法将左值绑定到右值引用。

int x=10;
int&& rr1 = x;

但是我们通过move关键字即可完成。
move仅仅是将变量的右值取出。

int x=10;
int&& rr1 = std::move(x);

3.模板右引用

对于模板来说,对于它&&,此时则既可以接受左值传参,也可以接受右值传参。也就是万能引用。

void Fun(int &x){ cout << "左值引用" << endl; }
void Fun(const int &x){ cout << "const 左值引用" << endl; }
void Fun(int &&x){ cout << "右值引用" << endl; }
void Fun(const int &&x){ cout << "const 右值引用" << endl; }
template<class T>
void func(T&& value)
{
	fun(value);
}

如果我们执行程序可以发现,我们的输出全是左值引用,这是为什么呢?
模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力, 但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值。
因此,需要我们的完美转发。

4.完美转发

void Fun(int &x){ cout << "左值引用" << endl; }
void Fun(const int &x){ cout << "const 左值引用" << endl; }
void Fun(int &&x){ cout << "右值引用" << endl; }
void Fun(const int &&x){ cout << "const 右值引用" << endl; }
// std::forward<T>(t)在传参的过程中保持了t的原生类型属性。
template<typename T>
void PerfectForward(T&& t)
{
 Fun(std::forward<T>(t));
}

通过forward<模板>(参数)即可实现完美转发,使左值按左值传参,右值按右值传参。

5.移动构造和移动赋值

移动构造和移动赋值
这样写避免了某些不必要的深拷贝。
noexcept关键字,表示该函数不会抛出异常。
注意赋值完,要对原对象进行处理,这里指针将其变为nullptr

3.初始化列表

1.初始化列表的特性

  1. 效率优势
    初始化列表在对象成员的初始化过程中效率更高。对于一些内置类型和没有默认构造函数的类类型成员,如果在初始化列表中进行初始化,能避免额外的默认初始化和赋值操作。
  2. 初始化顺序
    成员变量的初始化顺序是按照它们在类中的声明顺序,而不是在初始化列表中的顺序。
  3. 成员对象初始化
    对于类中的成员对象,使用初始化列表可以正确地调用其对应的构造函数进行初始化。
  4. 避免未定义行为
    如果不使用初始化列表对某些成员进行初始化,可能导致未定义的行为,尤其是对于具有复杂依赖关系的成员。

2.哪些类型必须在初始化列表中初始化?

  1. const成员变量
class MyClass {
    const int myConstVar;
public:
    MyClass(int val) : myConstVar(val) {}  // 必须在初始化列表中初始化
};
  1. 引用成员变量
class YourClass {
    int& myRefVar;
public:
    YourClass(int& var) : myRefVar(var) {}  // 引用必须在初始化列表中初始化
};
  1. 没有默认构造函数的类成员
class OtherClass {
    OtherClass(int x) {}  // 自定义构造函数,无默认构造函数
};

class MainClass {
    OtherClass myOther;
public:
    MainClass(int val) : myOther(val) {}  // 需在初始化列表中初始化
};
  1. 基类
    当从基类派生出一个类,并且基类没有默认构造函数时,则必须在初始化列表中进行初始化。
class Base {
    Base(int num) {}
};

class Derived : public Base {
public:
    Derived(int num) : Base(num) {}  // 初始化基类
};

4.运算符重载

1. 不支持重载的运算符

  1. 作用域操作符 ::
  2. 条件操作符 ?:
  3. 点操作符 .
  4. 预处理操作符 #

2.自定义类型转化内置类型

可以通过重载内置类型来完成
例如,以下代码将MyClass类转化为int类型,此时重载函数不需要写返回值。

class MyClass {
private:
    int value;

public:
    MyClass(int val) : value(val) {}

    operator int() {
        return value;
    }
};

int main() {
    MyClass obj(42);
    int num = obj;  // 调用重载的 operator int 进行类型转换
    std::cout << "Converted value: " << num << std::endl;

    return 0;
}

3. 内存管理

1.new delete 和 malloc free的区别

区别

2.new 和 new[]底层实现原理

  1. new 会调用operator new 先去开辟空间,开辟完后再调用对应的构造函数进行初始化。
  2. new [] 会调用n次operator new去开辟空间,然后再去调用n次构造函数进行初始化。

3.内存泄露问题

1.什么是内存泄漏?

内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
由于引入了异常问题,在抛出异常后对应的内存可能没有进行释放,久而久之就可能导致内存泄漏。

2.内存泄漏的危害

长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现
内存泄漏会导致响应越来越慢,最终卡死。

3.如何解决内存泄漏

1.RAII 智能指针
  1. auto_ptr
    最早引入的智能指针,但不支持赋值
  2. smart_ptr
    支持赋值,但是双链表的循环会出现问题。
  3. weak_ptr
    不增加计数,用来解决循环问题。
2.使用内存泄漏检查工具

总结

以上就是C/C++的总结一,接下来应该还有需要补充修改的内容。
本人小白一枚,有问题还望各位大佬指正。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部