C++常见知识点疑惑整理-创新互联

1 C++常见知识点 内联函数
  • inline 是一种“用于实现的关键字”。

关键字inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用。如下风格的函数Foo不能成为内联函数:

简阳网站建设公司成都创新互联,简阳网站设计制作,有大型网站制作公司丰富经验。已为简阳1000多家提供企业网站建设服务。企业网站搭建\外贸网站制作要多少钱,请找那个售后服务好的简阳做网站的公司定做!
inline void Foo(int x, int y); // inline 仅与函数声明放在一起,不可以!
void Foo(int x, int y){}

而如下风格的函数Foo则可以成为内联函数:

void Foo(int x, int y);
inline void Foo(int x, int y) {} // inline 与函数定义体放在一起

因此,inline 是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。

const和constexpr

参考

const:常量,可以理解为常变量。不要求编译的时候就计算出来,可以由变量赋值。const修饰的可能是编译期就能确定的也可能是运行期才确定。参考

constexpr:常量表达式,编译时就要被计算出。给了编译器一个优化代码的机会! constexpr还可以修饰函数的返回值,要求在编译的时候就把函数的返回值计算出来。否则编译器会等到运行的时候再去计算结果。constexpr只能修饰字面类型(常见的基本类型)

int x = 10;

const int i = x * x;        正确
constexpr int j = 10;       正确

int a[i];					错误,i不是常量表达式
int b[j];					正确,j是常量表达式
什么是运行时什么是编译时

参考

1)常数值在编译时就确定,变量值要到运行时才确定;
2)局部变量相对于栈基址的偏移,编译时就确定,堆空间变量的相对位置也要运行时才确定;
3)静态变量在用户空间的地址【虚拟地址】编译时就确定,而非静态变量运行时才确定;
4)非虚函数的执行编译时就确定,而虚函数的执行运行时才确定;
5)直接调用虚函数时,虚函数在虚表中的偏移(或索引)编译时就确定,而通过函数指针调用虚函数时,这个索引值运行时才确定.

c++不在运行时做数组越界检查,因为这样可以提高效率。

🍎

int arr[] = {1,2,3};
int result = arr[4];
cout<

这个代码在编译的时候不会报错。

在运行的时候也不会报错,只会把不该读的内存读出来给你看。

但是点击Debug的时候,能检查出来,但这不是编译器的功劳,是一些vs中的工具做的工作。

char与unsigned char

参考

有什么区别呢?

共同点:

  • 都是8 bit
  • 不管是什么类型,在机器中存储的二进制码一模一样。

区别:

  • 对于不同类型,机器翻译的方式不一样。如果是unsigned char,直接按照原码翻译。如果是signed char,考虑符号位计算。

  • 如果解读为其他类型,对于unsigned char不需要进行符号位扩展,而对于signed char需要进行符号位扩展,符号位为1就1扩展,为0就0扩展。

  • 因为解析方式不同,导致其各自取值范围也不同。

    unsigned char 最小0000 0000为0,大1111 1111为255

    signed char最小为1000 0000为-128,大为0111 1111为27-1=127

🍏以0x81为例, 其二进制码为1000 0001:

signed char: -128+1 = -127

unsigned char: 128+1 = 129

int32:1111 1111 1111 1111 1111 1111 1000 0001:-1-0111 1110=-1 - 126= -127

关于原码计算和补码计算

参考

🍏以0x81为例, 其二进制码为1000 0001:

如果是signed char,则目前的这个0x81也就是1000 0001是一个补码。

补码要怎么计算出它到底表示多少呢?

有两种计算方式:

  • 直接拿补码进行计算:-1*128 +1 = -127
  • 还原成原码,再以原码的形式计算。其原码的计算方式为:减1,除符号位外取反得到1111 1111, 原码怎么解析这个数?最前面的1不参与计算,直接表示负。后面的是26+25+24+23+22+21+1=127,加上前面的符号就是-127了。

所以只要会一种方式就行了,建议直接第一种方式。

整数溢出

数据超过了该类型能表示的上下限
对于有符号数:
下界溢出:MIN-1 = MAX
上界溢出:MAX+1 = MIN
对于无符号数:
下界溢出:MIN-1= MAX
上界溢出:MAX+1 = 0, 高位截断

有符号整数溢出
有符号整数参与加减乘除时,算术结果无法表示成想要的结果的类型,就叫整数溢出。比如相加结果比MAX还大,这种情况可能会产生不正确的值。比如除以0,这可能会使程序退出运行。除以0这个就是C++标准里面没有定义的行为。
无符号整数回绕
无符号数溢出的时候,就会+1对MAX取模。这个规则使整数回绕是可移植的(前提是相同位数)。
💡外部传入的数据不可信,参与计算时如何避免溢出?

很好的办法就是不让外部数据直接参与加减乘除运算,而是作为被比较的一边。

2 编程实践 1 函数
  • 当输入或返回的对象总是有效时,优先使用引用取代指针。引用一定是非空,不需要再做判空。

    ⚠️引用做函数形参,传递一个nullptr进去,编译不出错,运行出错。

    ⚠️传递一个未初始化的变量名进去,编译无错,运行无错,可能就是会打印出来的东西是空的。

  • 如果返回值可能是无效的,可以使用std::optional做为返回值

  • 信任边界:跨越信任边界都应该做入参检查,不同的so/dll都属于信任边界。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vciWcSA3-1672916488147)(C:\Users\j00808041\AppData\Roaming\Typora\typora-user-images\image-20230103145644306.png)]

2 类 特殊成员函数
  • 如果类内没有资源申请和释放,就不要自定义析构函数,不要自定义拷贝、拷贝赋值、移动、移动赋值函数。

  • 如果定义了拷贝构造、拷贝赋值或者析构函数,就要同时定义移动构造、移动赋值。因为编译器不会再自动生成移动构造和移动赋值。如果不定义就会导致这个类的移动操作也变成了拷贝。除非确保不用移动相关的。

  • 综上,拷贝和移动,要定义就都定义,要么就都不定义。但是如果涉及类内成员的内存申请和释放就必须定义析构函数。定义析构就必须定义拷贝、拷贝赋值,也就必须定义移动、移动赋值。

  • 拷贝构造、拷贝赋值必须同时声明。同delete或者同default。比如,不想让子类转化为基类,就禁止四类特殊构造函数,=delete,防止切片。

  • 移动构造移动赋值必须同时声明。同delete或者同default。

3 枚举

C++在C的基础上使用了enum struct 和enum class,是真正的强类型,枚举值不会被隐式转换成整数类型,可以使用static_cast。

如果完全不初始化,就会从0开始

如果部分初始化后面会在第一个初始化的数逐个加一;

所有项都进行初始化。

枚举的命名和枚举类型名是在同一个作用域下面的,因此会隐藏掉外面的相同的变量名。

4 计算 提高类型再计算表达式

整型表达式比较或赋值为一种更大类型之前必须用这种更大类型对它进行求值 .

由于整数在运算过程中可能出现有符号整数溢出、无符号整数回绕等问题,当运算结果赋值给比它更大的类型,或者与比它更大的类型进行比较时,可能会导致实际结果与预期结果不符。如果将涉及某个操作的整数表达式与较大的整数进行比较或者赋值给较大的整数,则应该将这个整数表达式中的一个操作数显式转换为较大的整数类型。

uint32_t a = 0xffffffff;
  uint32_t b = 1;
  cout<< a<< endl;//4294967295
  cout<< a + b<< endl;//0
  cout<< static_cast(a) + static_cast(b)<< endl;//4294967296

先提高类型再进行计算!

比较浮点数是否相等
bool IsEqual(double a, double b)
{
  return (fabs(a-b)<= std::numeric_limits::epsilon());
}
避免多余== 和!=

if(true)

if(!false)

比较表达式倾向左边变化右边不变化 5 资源访问 避免lambda表达式使用默认捕获模式

两种默认捕获:按引用捕获&、按值=捕获

按值捕获,如果在类的成员函数内部,会隐式捕获this指针。

按引用捕获,会不会所有局部变量,容易造成访问悬空指针。❓如果仅仅是在内部使用,为什么会造成访问悬空指针?

指向资源句柄和描述符的变量 释放后立即赋新值 使用智能指针管理资源表示所有权的转移或者共享 6 标准库 不要保存std::string类型的c_str和data成员函数返回的指针

假如先用一个指针

string test = "abc";
const char* receive = test.c_str();
//后面修改了test 可能会导致此时receive的地址空间已经不合法
test[1] = 'd';
DoSomething(receive);

//应该现用现调 调用的开销会被编译器优化
DoSomething(test.c_str());
禁止使用string存储口令等敏感信息’

std::string类是C++内部定义的字符串管理类,如果口令等敏感信息通过std::string进行操作,在程序运行过程中,敏感信息可能会散落到内存的各个地方,并且无法清除。

🍎测试:

bool verify(string password) //传值
{
  cout<< &password<< endl;
  return true;
}
int main() {
  string in = "as";
  cout<< &in<< endl; //0x61fdc0
  verify(in);	//0x61fdf0
  cout<< &in<< endl; //0x61fdc0
  system("pause");
  return 0;
}
bool verify(string &password) //传引用
{
  cout<< &password<< endl;
  return true;
}
int main() {
  string in = "as";
  cout<< &in<< endl; //0x61fde0
  verify(in); 				 //0x61fde0
  cout<< &in<< endl;//0x61fde0
  system("pause");
  return 0;
}

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


当前题目:C++常见知识点疑惑整理-创新互联
文章网址:http://hbruida.cn/article/dssdhs.html