C C++知识点总结

一.C语言

1.变量的声明和定义的区别

定义变量时为变量分配地址,声明变量时不分配,一个变量可以在多个地方声明,但只能在一个地方定义。

2.sizeof和strlen的区别

(1)sizeof是一个操作符,strlen是库函数
(2)sizeof的参数可以是数据的类型,也可以是变量,而strlen的参数只能是以’\0’为结尾的字符串指针。

3.写一个比较大小的宏
    #define min(x, y) ({				\
        typeof(x) _min1 = (x);			\
        typeof(y) _min2 = (y);			\
        (void) (&_min1 == &_min2);		\
        _min1 < _min2 ? _min1 : _min2; })

    #define max(x, y) ({				\
        typeof(x) _max1 = (x);			\
        typeof(y) _max2 = (y);			\
        (void) (&_max1 == &_max2);		\
        _max1 > _max2 ? _max1 : _max2; })
4.volatile的作用

volatile修饰的变量随时都有可能改变,告诉编译器不要优化,每次存取该变量都要从内存中操作,不要保存到寄存器中。如果没有volatile关键字,则编译器可能优化该变量,直接从寄存器中存取,如果该变量在其他地方被更新了,将造成数据不一致现象。(简洁的说就是:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错)
volatile的应用场合:
(1)存储器映射的硬件寄存器通常要加volatile说明
(2)多任务环境下各任务共享的标志,应该加volatile
(3)中断服务程序中修改其他程序检测的变量,需要加volatile
一个参数可以既是const又是volatile吗?
可以,如硬件的只读状态寄存器,volatile修饰因为可能会被改变,const修饰表示程序不应该修改。

5.请解析((void ()( ) )0)( )的含义

void (*0)( ) : 是一个返回值为void,参数为空的函数指针0。
(void (*)( ))0: 把0转变成一个返回值为void,参数为空的函数指针。
*(void (*)( ))0: 在上句的基础上加*表示整个是一个返回值为void,无参数,并且起始地址为0的函数的名字。
(*(void (*)( ))0)( ): 这就是上句的函数名所对应的函数的调用。

6.编码实现字符串转化为数字
    #define MIN_INT  (-0x7FFFFFFF - 1)
    #define MAX_INT  (0x7FFFFFFF)
    int my_atoi(const char* nptr)
    {
        const char* s = nptr;
        unsigned char c;
        unsigned long acc = 0, cutoff;
        int neg, cutlim;

        if (NULL == nptr) {
            printf("nptr is NULL\n");
            return 0;
        }

        while (' ' == *s)  // 跳过字符串开头的空格
            ++s;

        if ('-' == *s) {
            neg = 1;  // 记录符号位,1为负,0为正
            ++s;
        } else if ('+' == *s) {
            ++s;
            neg = 0;
        } else {
            neg = 0;
        }

        cutoff = (neg ? -(unsigned long)MIN_INT : MAX_INT);
        cutlim = cutoff % 10;
        cutoff /= 10;

        for (c = *s; 0 != c; c = *++s) {
            if ((c >= '0') && (c <= '9')) {
                c -= '0';
            } else {
                printf("string error, error ch %c\n", (char)c);
                return 0;
            }

            if ((acc > cutoff) || (acc == cutoff) && (c > cutlim)) {
                printf("string max int\n");
                acc = (neg ? MIN_INT : MAX_INT);
                return acc;
            } else {
                acc *= 10;
                acc += c;
            }
        }
        if (neg)
            acc = -acc;

        return acc;
    }

二.C语言和C++语言

1.static关键字在C和C++中的相同和不同

在C语言中static关键字可修饰变量和函数,在C++中除了具有C语言的作用外,还可修饰类的成员函数和成员变量。
C语言中的功能如下:
(1)当static修饰局部变量时,局部变量的生命周期和全局变量的声明周期一致,程序结束时才会被回收,未初始化的static局部变量存储在bss段,程序运行时由操作系统执行默认初始化,初始化的static局部变量存储在data段。
(2)当static修饰全局变量时,限定此变量的作用域只在定义的源文件中,其他源文件无法使用。
(3)当static修饰函数时,限定此函数的作用域只在定义的源文件中,其他源文件无法使用。
C++除了包含C语言的功能,还有如下:
(1)当static修饰类的成员变量时,表明该成员变量在类的所有对象中都只有一个实例,即该实例归所有对象共有。
(2)当static修饰类的成员函数时,此成员函数变为静态成员函数,静态成员函数不可以访问非静态成员函数和非静态成员变量。这意味着一个静态成员函数只能访问它的参数、静态成员变量、静态成员函数和全局变量。

2.C语言malloc和C++语言new的区别

(1)new、delete是操作符,可以重载,只能在C++中使用
(2)new调用类的构造函数,delete调用类的析构函数
(3)new操作符执行包含三步,第一调用malloc分配内存,第二调用类的构造函数构造对象,第三将malloc返回的类型转换为对应类的类型
(4)malloc、free是函数,C、C++中都可以使用
(5)malloc分配的内存要用free释放,new申请的内存空间要用delete释放

3.指针和引用有什么区别?

(1)指针定义时可初始化可不初始化,引用定义时必须初始化
(2)指针是变量,可以改变,引用是变量的别名,初始化完毕后不可改变
(3)使用sizeof计算指针的大小,得到的是指针本身占用的空间大小,而引用则是被引用对象的空间大小
(4)指针可以有多级指针(**p),而引用止于一级

4.struct和class的区别

(1)struct
strcut中的默认成员访问说明符和默认派生访问说明符是public。

(2)class
class中的默认成员访问说明符和默认派生访问说明符默认是private。
class还可用于定义模板参数,像typename。

三.C++语言

1.C++的空类默认有哪些构造函数呢?空类大小?

默认构造函数、默认拷贝构造函数、默认析构函数、默认赋值运算符、取址运算符和const类型的取址运算符。这些函数只有在第一次被调用时,才会被编译器创建。所有这些函数都是inline和public的。
C++中要求对于类的每个实例都必须有独一无二的地址,那么编译器自动为空类分配一个字节大小,这样便保证了每个实例均有独一无二的内存地址。

2.什么是拷贝构造函数?

由编译器调用来完成一些基于同一类的其他对象的构建及初始化。其形参必须是引用,但并不限制为const,一般普遍的会加上const限制。在C++中,下面三种对象需要调用拷贝构造函数(有时也称“复制构造函数”):
(1)一个对象作为函数参数,以值传递的方式传入函数体
(2)一个对象作为函数返回值,以值传递的方式从函数返回
(3)一个对象用于给另外一个对象进行初始化(常称为赋值初始化)

3.深拷贝和浅拷贝的区别

(1)深拷贝
当拷贝对象中含有资源(如堆、文件、系统等)的引用或指针时,新对象将会重新分配资源,而不是简单的将拷贝对象中资源的引用或指针拷贝到新对象中。
(2)浅拷贝
在对象复制时,只对对象中的数据成员进行一一赋值,默认拷贝构造函数执行的也是浅拷贝。

4.C++多态是通过什么机制实现的?

(1)多态
多态性可以简单的概括为“1个接口,多种方法”,在程序运行时才决定使用哪个方法。通过父类指针或引用调用子类的方法实现多态。
  (a)用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数。
  (b)存在虚函数的类都有一个一维的虚函数表叫做虚表,类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应。
  (c)多态性是一个接口多种实现,是面向对象的核心,分为类的多态性和函数的多态性。
  (d)多态用虚函数来实现,结合动态绑定。
  (e)纯虚函数是虚函数再加上 = 0。
  (f)抽象类是指包括至少一个纯虚函数的类。纯虚函数:virtual void fun()=0,必须在子类实现这个函数,即先有名称,没有内容,在派生类实现内容。

(2)多态实现机制
编译器为每个类的对象提供一个虚表指针,这个指针指向对象所属类的虚表。在程序运行时,根据对象的类型去初始化虚表指针,从而让虚表指针正确的指向所属类的虚表,从而在调用虚函数时,就能够找到正确的函数。

5.多态有什么安全性问题?
6.lambda表达式有什么安全性问题?

Lambda表达式使用引用捕获某个局部变量,而调用Lambda表达式时,局部变量已经被清理导致,捕获的引用指向被清理的内存空间产生悬挂引用。捕获了this,但是可能在使用Lambda时this指向的对象已经被销毁,从而产生悬挂引用。

7.智能指针的底层实现原理

(1)RAII思想
  (a)定义一个类来封装资源的分配与释放
  (b)构造函数中完成资源的分配及初始化
  (c)析构函数中完成资源的清理,可以保证资源的正确初始化和释放
  (d)如果对象是用声明的方式在栈上创建局部对象,那么RAII机制就会正常工作,当离开作用域对象会自动销毁而调用析构函数释放资源。
(2)智能指针的特点
  (a)具有RAII的思想
  (b)向指针一样,可以使其指向对象成员,并且重载了*和->运算符
  (c)是对资源的一种管理,不是拥有
  (3)使用引用计数,拷贝智能指针时增加引用计数的值,析构智能指针时减少引用计数的值,当引用计数为0时释放智能指针管理的内存。

8.右值和左值的区别,右值引用时什么?

(1)左值:能取地址、有名称的对象,表达式结束后依然存在的持久对象
(2)右值:不能取地址、匿名对象,表达式结束后就不存在的零时对象
(3)左值引用:变量的别名,可理解为常量指针,可以出现在=的左边
(4)右值引用:C++11新增的概念,用&&表示,只能出现在=的右边,右值引用则是对一个右值进行引用的类型,右值引用主要用于移动语义和完美转发

9.面向对象的三大特征

(1)封装
将客观事物抽象成类,封装类的属性和实现细节,仅对外公开接口,同时控制类的访问级别。封装的目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过外部接口,以特定的访问权限来使用类的成员。
(2)继承
继承机制允许创建分等级层次的类。继承就是子类继承父类的特征和行为,使得子类具有父类的某些特性。
(3)多态
多态是指同一个行为具有多个不同表现形式的能力。是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。简单说就是子类调用父类的方法,不同的子类会产生不同的结果。

10.面向对象的五大基本原则

(1)单一职责原则(SRP)
一个类应该有且只有一个去改变它的理由,这意味着一个类应该只有一项工作。比如在职员类里,将工程师、销售人员、销售经理这些情况都放在职员类里考虑,其结果将会非常混乱,在这个假设下,职员类里的每个方法都要if else判断是哪种情况,从类结构上来说将会十分臃肿。
(2)开放封闭原则(OCP)
对象或实体应该对扩展开放,对修改封闭。更改封闭即是在我们对模块进行扩展时,勿需对源有程序代码和DLL进行修改或重新编译文件!这个原则对我们在设计类的时候很有帮助,坚持这个原则就必须尽量考虑接口封装,抽象机制和多态技术。
(3)里氏替换原则(LSP)
在对象x为类型T时q(x)成立,那么当S是T的子类时,对象y为类型S时q(y)也应成立。(即对父类的调用同样适用于子类)。
(4)依赖倒置原则(DIP)
高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。具体实现应该依赖于抽象,而不是抽象依赖于实现。可以这样理解,上面我举例子的时候先说了兔子和绵羊,然后才推出食草动物。但如果我们继续认识了牛、马等食草动物,我们会发现我们需要不断调整食草动物的描述,这样程序会变得僵化,所以我们不应该让子类依赖于实体,不应该让父类模块依赖于子类模块。所以我们需要将食草动物设计为抽象类,即抽象类或接口。这样下层只需要实现相应的细节而不会影响父类。
(5)接口隔离原则(ISP)
不应强迫客户端实现一个它用不上的接口,或是说客户端不应该被迫依赖它们不使用的方法,使用多个专门的接口比使用单个接口要好的多。

11.C++的4中cast转换

(1)static_cast
任何具有明确定义的类型转换,只要不包含底层的const,都可以使用static_cast。用于非多态类型的转换,不执行运行时类型检查,通常用于数指数据类型的转换。
(2)const_cast
const_cast只能改变运算对象的底层const,对于将常量对象转换成非常量对象的行为,一般被称为去掉const性质。
(3)reinterpret_cast
reinterpret_cast通常为运算对象的位模式提供较低层次上的重新解释。它会产生一个新的值,这个值会有与原始参数(expression)有完全相同的比特位。reinterpret_cast用在任意指针(或引用)类型之间的转换;以及指针与足够大的整数类型之间的转换;从整数类型(包括枚举类型)到指针类型,无视大小。
(4)dynamic_cast
用于将基类的指针或引用安全地转换成派生类的指针或引用。主要还是执行安全的向下转型。dynamic_cast依赖于RTTI(run-time type identification,运行时类型识别)信息,在转换时dynamic_cast会检查转换的source对象是否真的可以转换成target类型。若转换失败,则抛出bad_cast异常。

12.拷贝构造函数和赋值运算符的区别

(1)拷贝构造函数生成新的类对象,而赋值运算符不能。
(2)由于拷贝构造函数是直接构造一个新的类对象,所以在初始化这个对象之前不用检验源对象 是否和新建对象相同。而赋值运算符则需要这个操作,另外赋值运算中如果原来的对象中有内存分配要先把内存释放掉。

13.实现一个string类
	/*=====================================my_string.h========================================*/
	#ifndef MY_STRING_H
	#define MY_STRING_H
	#include <cstddef>
	#include <iostream>
	class My_string {
		friend std::ostream& operator<<(std::ostream& os, const My_string& str);
	public:
		My_string(const char* str = nullptr);  // 默认构造函数
		~My_string() { delete[] m_str; }  // 析构函数
		My_string(const My_string& str);  // 拷贝构造函数
		My_string& operator=(const My_string& str);  // 拷贝赋值运算符
		My_string& operator=(const char* str);  // 拷贝赋值运算符
		My_string& operator+=(const My_string& str);
		My_string& operator+=(const char* str);
		char& operator[](size_t pos) { return m_str[pos]; }
		const char& operator[](size_t pos) const { return m_str[pos]; }
	private:
		char* m_str;
		size_t m_size;
	};
	
	My_string operator+(const My_string& lhs, const My_string& rhs);
	std::ostream& operator<<(std::ostream& os, const My_string& str);
	#endif  // MY_STRING_H

	/*====================================my_string.cpp=======================================*/
	#include "my_string.h"
	#include <cstring>
	#include <cstdlib>
	
	My_string::My_string(const char* str)
	{
		if (nullptr == str) {
			m_str = new char[1];
			m_str[0] = '\0';
			m_size = 0;
		} else {
			m_size = strlen(str);
			m_str = new char[m_size + 1];
			strcpy(m_str, str);
		}
	}
	
	My_string::My_string(const My_string& str)
	{
		m_size = str.m_size;
		m_str = new char[m_size + 1];
		strcpy(m_str, str.m_str);
	}
	
	My_string& My_string::operator=(const My_string& str)
	{
		if (this != &str) {  // 处理自我赋值的情况
			char* tmp = new char[str.m_size + 1];
			strcpy(tmp, str.m_str);
			m_size = str.m_size;
			delete[] m_str;  // 释放原有的内存
			m_str = tmp;
		}
		return *this;
	}
	
	My_string& My_string::operator=(const char* str)
	{
		if (nullptr == str)
			return *this;
	
		if (m_str != str) {  // 处理自我赋值的情况
			size_t size = strlen(str);
			char* tmp = new char[size + 1];
			strcpy(tmp, str);
			m_size = size;
			delete[] m_str;  // 释放原有的内存
			m_str = tmp;
		}
		return *this;
	}
	
	My_string& My_string::operator+=(const My_string& str)
	{
		if (0 != str.m_size) {
			char* tmp = new char[m_size + str.m_size + 1];
			memcpy(tmp, m_str, m_size);
			strcpy(&tmp[m_size], str.m_str);
			m_size += str.m_size;
			delete [] m_str;
			m_str = tmp;
		}
		return *this;
	}
	
	My_string& My_string::operator+=(const char* str)
	{
		if (nullptr == str)
			return *this;
	
		size_t size = strlen(str);
		if (0 != size) {
			char* tmp = new char[m_size + size + 1];
			memcpy(tmp, m_str, m_size);
			strcpy(&tmp[m_size], str);
			m_size += size;
			delete [] m_str;
			m_str = tmp;
		}
		return *this;
	}
	
	My_string operator+(const My_string& lhs, const My_string& rhs)
	{
		My_string s(lhs);
		return (s += rhs);
	}
	std::ostream& operator<<(std::ostream& os, const My_string& str)
	{
		os << str.m_str;
		return os;
	}
	/*====================================main.cpp=======================================*/
	#include <iostream>
	#include "my_string.h"
	int main()
	{
		My_string s1("sssssssss1111111111");
		std::cout << "s1: " << s1 << '\n';
	
		My_string s2(s1);
		std::cout << "s2: " << s2 << '\n';
	
		My_string s3("sssssssss3333333333");
		My_string s4 = s1 + s3;
		std::cout << "s4: " << s4 << '\n';
	
		My_string s5("sssssssss555555555");
		s5 += s4;
		std::cout << "s5: " << s5 << '\n';
		std::cout << "s5: s5[0] " << s5[0] << " s5[10]: " << s5[10] << '\n';
		return 0;
	}

	/*====================================Makefile=======================================*/
	CFLAGS := -Wall -O2 -std=c++11
	OBJS := main.o my_string.o
	
	my_string: $(OBJS)
		g++ $(OBJS) -o my_string
	main.o : main.cpp my_string.h
		g++ $(CFLAGS) -c main.cpp -o main.o
	my_string.o : my_string.cpp my_string.h
		g++ $(CFLAGS)  -c my_string.cpp -o my_string.o
	
	.PHONY: clean
	clean:
		rm -rf my_string $(OBJS)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值