C++98&C++11的区别

文章目录

  • C++98&C++11的区别
  • 1. C++11简介
  • 2. 自动类型推导auto
  • 3. 列表初始化
  • 4. 变量类型推导
  • 5. 范围for循环
  • 6. 返回类型后置语法
  • 7. final和override
  • 8. =default和=delete
  • 9. lambda表达式
  • 10. std::move
  • 11. std::array
  • 12. std::forward_list
  • 13. std::unordered_map和std::unordered_set
  • 14. 智能指针
  • 15. 右值引用&线程库

1. C++11简介

在2003年C++标准委员会曾经提交了一份技术勘误表(简称TC1),使得C++03这个名字已经取代了C++98称为C++11之前的最新C++标准名称。不过由于TC1主要是对C++98标准中的漏洞进行修复,语言的核心部分则没有改动,因此人们习惯性的把两个标准合并称为C++98/03标准。从C++0x到C++11,C++标准10年磨一剑,第二个真正意义上的标准珊珊来迟。相比于C++98/03,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言相比较而言,C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率

2. 自动类型推导auto

auto的自动推导,是编译器在编译阶段根据初始化表达式中的变量类型推断出具体的数据类型。可以理解为一个类型‘占位符’

//C++98
int a = 10;
string s = "abc";
float b = 10.0;
vector<int> c;
vector<vector<int> > d;
map<int, string> m;
m[1] = "aaa";
map<int, string>::iterator it = m.begin();//C++11
auto a1 = 10;  //a1为int
auto s1 = "abc";  //s1为string
auto b1 = b;
auto c1 = c;
auto d1 = d;
auto e1 = 'a';
int* x = &a1;
auto d1 = x;
auto m1 = m.begin();
auto x=1,y=2; //ok
auto i=1.j=3.14; //compile errordouble a2 = 3.144;
const auto a3 = a2;  //const double
auto a4 = a2;  //double
volatile int c2 = 3;
auto c3 = c2; //int

3. 列表初始化

3.1 C++98中{}的初始化问题:

在C++98中,标准允许使用花括号{}对数组元素进行统一的列表初始值设定。比如:

int array1[] = {1,2,3,4,5};
int array2[5] = {0};

对于一些自定义的类型,却无法使用这样的初始化。比如:

vector<int> v{1,2,3,4,5};

就无法通过编译,导致每次定义vector时,都需要先把vector定义出来,然后使用循环对其赋初始值,非常不方便。

C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。

3.2 内置类型的列表初始化:

int main()
{// 内置类型变量int x1 = {10};int x2{10};int x3 = 1+2;int x4 = {1+2};int x5{1+2};// 数组int arr1[5] {1,2,3,4,5};int arr2[]{1,2,3,4,5};// 动态数组,在C++98中不支持int* arr3 = new int[5]{1,2,3,4,5};// 标准容器vector<int> v{1,2,3,4,5};map<int, int> m{{1,1}, {2,2,},{3,3},{4,4}};return 0;
}

注意:列表初始化可以在{}之前使用等号,其效果与不使用=没有什么区别

3.3 自定义类型的列表初始化:

  • 标准库支持单个对象的列表初始化
class Point
{public:Point(int x = 0, int y = 0): _x(x), _y(y){}
private:int _x;int _y;
};
int main()
{Pointer p{ 1, 2 };return 0;
}
  • 多个对象的列表初始化

多个对象想要支持列表初始化,需给该类(模板类)添加一个带有initializer_list类型参数的构造函数即可。注意:initializer_list是系统自定义的类模板,该类模板中主要有三个方法:begin()、end()迭代器以及获取区间中元素个数的方法size()

#include <initializer_list>
template<class T>
class Vector {public:// ...Vector(initializer_list<T> l): _capacity(l.size()), _size(0){_array = new T[_capacity];for(auto e : l)_array[_size++] = e;}Vector<T>& operator=(initializer_list<T> l) {delete[] _array;size_t i = 0;for (auto e : l)_array[i++] = e;return *this;}// ...
private:T* _array;size_t _capacity;size_t _size;
};

4. 变量类型推导

4.1 为什么需要类型推导:

在定义变量时,必须先给出变量的实际类型,编译器才允许定义,但有些情况下可能不知道需要实际类型怎么给,或者类型写起来特别复杂, 比如:

#include <map>
#include <string>
int main()
{short a = 32670;short b = 32670;// c如果给成short,会造成数据丢失,如果能够让编译器根据a+b的结果推导c的实际类型,就不会存在问题short c = a + b;std::map<std::string, std::string> m{{"apple", "苹果"}, {"banana","香蕉"}};// 使用迭代器遍历容器, 迭代器类型太繁琐std::map<std::string, std::string>::iterator it = m.begin();//auto it = m.begin();while(it != m.end()){cout<<it->first<<" "<<it->second<<endl;++it;}return 0;
}

4.2 decltype类型推导:

  • 为什么需要decltype

auto使用的前提是:必须要对auto声明的类型进行初始化,否则编译器无法推导出auto的实际类型。 但有时候可能需要根据表达式运行完成之后结果的类型进行推导,因为编译期间,代码不会运行,此时auto也就无能为力

template<class T1, class T2>
T1 Add(const T1& left, const T2& right)
{return left + right;
}

如果能用加完之后结果的实际类型作为函数的返回值类型就不会出错,但这需要程序运行完才能知道结果的实际类型,即RTTI(Run-Time Type Identification 运行时类型识别)。

C++98中确实已经支持RTTI:

  • typeid只能查看类型不能用其结果类定义类型
  • dynamic_cast只能应用于含有虚函数的继承体系中

运行时类型识别的缺陷是降低程序运行的效率

  • decltype
    是根据表达式的实际类型推演出定义变量时所用的类型,比如:
  1. 推演表达式类型作为变量的定义类型
int main()
{int a = 10;int b = 20;// 用decltype推演a+b的实际类型,作为定义c的类型decltype(a+b) c;cout<<typeid(c).name()<<endl;return 0;
}
  1. 推演函数返回值的类型
void* GetMemory(size_t size)
{return malloc(size);
}
int main()
{// 如果没有带参数,推导函数的类型cout << typeid(decltype(GetMemory)).name() << endl;// 如果带参数列表,推导的是函数返回值的类型,注意:此处只是推演,不会执行函数cout << typeid(decltype(GetMemory(0))).name() <<endl;return 0;
}

5. 范围for循环

#include <iostream>
#include <vector>using namespace std;int main()
{//C++98vector<int> vec(8, 1);cout << "C++98 range for:" << endl;for (vector<int>::iterator it = vec.begin(); it != vec.end(); it++){cout << *it << endl;}//C++11cout << "C++11 range for:" << endl;for (auto d : vec){cout << d << endl;}return 0;
}

值得指出的是,是否能够使用基于范围的for循环,必须依赖一些条件。首先,就是for循环迭代的范围是可确定的。 对于类来说,如果该类有begin和end函数,那么for_each之间就是for循环迭代的范围。对于数组而言,就是数组的第一个和最后一个元素间的范围。其次,基于范围的for循环还要求迭代的对象实现+ + 和==等操作符。对于STL中的容器,如string、array、map等使用起来是不会有问题的。下面是C++11操作vector和数组的实践:

#include <iostream>
#include <vector>using namespace std;int main()
{vector<int> vec(8, 1);//C++11cout << "C++11 value range for:" << endl;/*d非引用,修改d不会影响vector里的值*/for (auto d : vec)   //d中存储的是vec中的值{d = 2;}for (auto d : vec)   {cout << d << endl;}cout << "C++11 reference range for:" << endl;/*当迭代变量d为引用时,vector里的值可以被修改*/for (auto &d : vec)  {d = 2;}for (auto d : vec)   {cout << d << endl;}//数组for_eachchar arr[] = {'a','b','c','d'};for (auto &d : arr){d -= 32;}for (auto d : arr){cout << d << endl;}//遍历二维数组,注意迭代变量row必须为引用。如果你想用 range for 的方法,来遍历更高维的数组 (dim > 2),那么你只需要:除了最内层循环之外,其他所有外层循环都加入 '&' 即可。int array2[5][5] = {0};for (auto &row : array2)for (auto col : row)cout << col << endl;return 0;
}

6. 返回类型后置语法

先看下面这个例子,编译器在推导decltype(t1+t2)时表达式中t1和t2都未声明,所以编译失败

#include <iostream>
#include <vector>using namespace std;template<class T1, class T2>
decltype(t1 + t2) sum(T1 t1, T2 t2)
{return t1 + t2;
}int main()
{auto total = sum(1, 2);cout << total << endl;return 0;
}

所以C++11引入新语法,即把函数的返回值移至参数声明之后,复合符号->decltype(t1+t2)被称为追踪返回类型。而原本的函数返回值由auto占据

#include <iostream>
#include <vector>using namespace std;template<class T1, class T2>
auto sum(T1 t1, T2 t2) ->decltype(t1+t2)
{return t1 + t2;
}int main()
{auto total = sum(1, 2);cout << total << endl;return 0;
}

7. final和override

struct B
{virtual void f1(int) const;virtual void f2();void f3();
};struct D1 : public B
{void f1(int) const override;  //okvoid f2(int) override;   //error,B中没有形如f2(int)的函数void f3() override;  //error,f3不是虚函数void f4() override;  //error,B中无f4函数
};struct D2 : public B
{void f1(int) const final;  //不许后续的其他类覆盖
};struct D3 :public D2
{void f2();void f1(int) const; //error,final函数不可覆盖
};

final还可以用于防止继承的发生

class NoDerived final
{};class Bad :NoDerived   //NoDerived不可做基类
{};class Base
{};class Last final :Base
{};class Bad2 :Last   //Last不可做基类
{};

8. =default和=delete

对于 C++ 的类,如果程序员没有为其定义特殊成员函数,那么在需要用到某个特殊成员函数的时候,编译器会隐式的自动生成一个默认的特殊成员函数,比如拷贝构造函数,或者拷贝赋值操作符

C++11允许我们 使用=default来要求编译器生成一个默认构造函数,也允许我们使用=delete来告诉编译器不要为我们生成某个默认函数

class B
{B() = default; //显示声明使用默认构造函数B(const B&) = delete; //禁止使用类对象之间的拷贝~B() = default;  //显示声明使用默认析构函数B& operator=(const B&) = delete;  //禁止使用类对象之间的赋值B(int a);
};

9. lambda表达式

1. C++98中的一个例子:

在C++98中,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法。

#include <algorithm>
#include <functional>
int main()
{int array[] = {4,1,8,5,3,7,0,9,2,6};// 默认按照小于比较,排出来结果是升序std::sort(array, array+sizeof(array)/sizeof(array[0]));// 如果需要降序,需要改变元素的比较规则std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());return 0;
}

如果待排序元素为自定义类型,需要用户定义排序时的比较规则:

struct Goods
{string _name;double _price;
};
struct Compare
{bool operator()(const Goods& gl, const Goods& gr){return gl._price <= gr._price;}
};
int main()
{Goods gds[] = { { "苹果", 2.1 }, { "香蕉", 3 }, { "橙子", 2.2 }, {"菠萝", 1.5} };sort(gds, gds+sizeof(gds) / sizeof(gds[0]), Compare());return 0;
}

随着C++语法的发展,人们开始觉得上面的写法太复杂了,每次为了实现一个algorithm算法, 都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。因此,在C11语法中出现了Lambda表达式。

lambda表达式:

int main()
{Goods gds[] = { { "苹果", 2.1 }, { "相交", 3 }, { "橙子", 2.2 }, {"菠萝", 1.5} };sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& l,    const Goods& r)->bool{return l._price < r._price;});return 0;
}

2. lambda表达式语法:

简单来说,Lambda函数也就是一个函数(匿名函数),它的语法定义如下:

[capture](parameters) mutable ->return-type{statement}
  • [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
  • (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起 省略
  • mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
  • ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分 可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
  • {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
int main()
{// 最简单的lambda表达式, 该lambda表达式没有任何意义[]{};// 省略参数列表和返回值类型,返回值类型由编译器推导为intint a = 3, b = 4;[=]{return a + 3; };// 省略了返回值类型,无返回值类型auto fun1 = [&](int c){b = a + c; };fun1(10)cout<<a<<" "<<b<<endl;// 各部分都很完善的lambda函数auto fun2 = [=, &b](int c)->int{return b += a+ c; };cout<<fun2(10)<<endl;// 复制捕捉xint x = 10;auto add_x = [x](int a) mutable { x *= 2; return a + x; };cout << add_x(10) << endl;return 0;
}

注意: 在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情

3. 捕获列表说明:

捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。

  • [var]:表示值传递方式捕捉变量var
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用传递捕捉变量var
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:表示值传递方式捕捉当前的this指针

注意:

a. 父作用域指包含lambda函数的语句块

b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量 [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量 c. 捕捉列表不允许变量重复传递,否则就会导致编译错误。 比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复

d. 在块作用域以外的lambda函数捕捉列表必须为空。

e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。

f. lambda表达式之间不能相互赋值,即使看起来类型相同

void (*PF)();
int main()
{auto f1 = []{cout << "hello world" << endl; };auto f2 = []{cout << "hello world" << endl; };// 此处先不解释原因,等lambda表达式底层实现原理看完后,大家就清楚了//f1 = f2; // 编译失败--->提示找不到operator=()// 允许使用一个lambda表达式拷贝构造一个新的副本auto f3(f2);f3();// 可以将lambda表达式赋值给相同类型的函数指针PF = f2;PF();return 0;
}

4. 函数对象与lambda表达式:

函数对象,又称为仿函数,即可以想函数一样使用的对象,就是在类中重载了operator()运算符的类对象

class Rate
{public:Rate(double rate): _rate(rate){}double operator()(double money, int year){ return money * _rate * year;}
private:double _rate;
};
int main()
{// 函数对象double rate = 0.49;Rate r1(rate);r1(10000, 2);// lamberauto r2 = [=](double monty, int year)->double{return monty*rate*year; };r2(10000, 2);return 0;
}
  • 从使用方式上来看,函数对象与lambda表达式完全一样。
  • 函数对象将rate作为其成员变量,在定义对象时给出初始值即可,
  • lambda表达式通过捕获列表可以直接将该变量捕获到

实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。

10. std::move

std::move是为性能而生,通过std::move,可以避免不必要的拷贝操作。std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝

#include <iostream>
#include <utility>
#include <vector>
#include <string>
int main()
{std::string str = "Hello";std::vector<std::string> v;//调用常规的拷贝构造函数,新建字符数组,拷贝数据v.push_back(str);std::cout << "After copy, str is \"" << str << "\"\n"; //After move, str is "Hello"//调用移动构造函数,掏空str,掏空后,最好不要使用strv.push_back(std::move(str));std::cout << "After move, str is \"" << str << "\"\n";   //After move, str is ""std::cout << "The contents of the vector are \"" << v[0]<< "\", \"" << v[1] << "\"\n";   //The contents of the vector are "Hello", "Hello"
}

11. std::array

  • 使用 std::array保存在栈内存中,相比堆内存中的 std::vector,我们就能够灵活的访问这里面的元素,从而获得更高的性能;同时正式由于其堆内存存储的特性,有些时候我们还需要自己负责释放这些资源
  • 使用std::array能够让代码变得更加现代,且封装了一些操作函数,同时还能够友好的使用标准库中的容器算法等等,比如 std::sort。
  • std::array 会在编译时创建一个固定大小的数组,std::array 不能够被隐式的转换成指针,使用 std::array 很简单,只需指定其类型和大小即可
#include <stdio.h>
#include <algorithm>
#include <array>void foo(int* p)
{}int main()
{std::array<int, 4> arr = {4,3,1,2};foo(&arr[0]);  //OKfoo(arr.data());  //OK//foo(arr);  //wrongstd::sort(arr.begin(), arr.end());  //排序return 0;
}

12. std::forward_list

std::forward_list 使用单向链表进行实现,提供了 $O(1)$ 复杂度的元素插入不支持快速随机访问(这也是链表的特点),也是标准库容器中唯一一个不提供 size() 方法的容器。当不需要双向迭代时,具有比 std::list 更高的空间利用率

#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <string>
#include <forward_list>int main()
{std::forward_list<int> list1 = { 1, 2, 3, 4 };//从前面向foo1容器中添加数据,注意不支持push_backlist1.pop_front();  //删除链表第一个元素list1.remove(3);   //删除链表值为3的节点list1.push_front(2);list1.push_front(1);list1.push_front(14);list1.push_front(17);list1.sort();for (auto &n : list1){if (n == 17)n = 19;}for (const auto &n : list1){std::cout << n << std::endl;  //1 2 2 4 14 19}return 0;
}

13. std::unordered_map和std::unordered_set

无序容器中的元素是不进行排序的,内部通过 Hash 表实现,插入和搜索元素的平均复杂度为 O(constant),在不关心容器内部元素顺序时,能够获得显著的性能提升。

C++11 引入了两组无序容器:std::unordered_map/std::unordered_multimap 和 std::unordered_set/std::unordered_multiset

下面给出unordered_map和unordered_set的使用方法。

#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <string>
#include <unordered_map>
#include <unordered_set>void foo(int* p)
{}int main()
{//unordered_map usagestd::unordered_map<std::string, int> um = { {"2",2},{"1",1},{"3",3} };//遍历for (const auto &n : um){std::cout << "key:" << n.first << "  value:" << n.second << std::endl;}std::cout << "value:" << um["1"] << std::endl;//unordered_set usagestd::unordered_set<int> us = { 2,3,4,1};//遍历for (const auto &n : us){std::cout << "value:" << n << std::endl;}std::cout << "value:" << us.count(9) << std::endl; //判断一个数是否在集合内,1存在0不存在std::cout << "value:" << *us.find(1) << std::endl;  //查找一个特定的数是否在集合内,找到就返回该数的迭代器位置return 0;
}

14. 智能指针

1. std::shared_ptr:

shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,删除所指向的堆内存。shared_ptr内部的引用计数是安全的,但是对象的读取需要加锁

#include <stdio.h>
#include <memory>
#include <iostream>int main()
{//auto ptr = std::make_shared<int>(10);std::shared_ptr<int> ptr(new int(10));std::shared_ptr<int> ptrC(ptr);auto ptr2 = ptr;{auto ptr3 = ptr2;std::cout << "pointer1.use_count() = " << ptr.use_count() << std::endl;  //4std::cout << "pointer2.use_count() = " << ptr2.use_count() << std::endl;  //4}std::cout << "pointer1.use_count() = " << ptr.use_count() << std::endl;  //3std::cout << "pointer2.use_count() = " << ptr2.use_count() << std::endl;  //3int *p = ptr.get(); //获取原始指针std::cout << "pointer1.use_count() = " << ptr.use_count() << std::endl;  //3std::cout << "pointer2.use_count() = " << ptr2.use_count() << std::endl;  //3return 0;
}

2. std::unique_ptr:

std::unique_ptr 是一种独占的智能指针,它禁止其他智能指针与其共享同一个对象,从而保证代码的安全

#include <stdio.h>
#include <memory>
#include <iostream>int main()
{std::unique_ptr<int> ptr(new int(10));//auto ptr2 = ptr; //非法//虽说unique_ptr是不可复制的,但我们可以使用std::move将其独占权转移到其他的unique_ptrauto ptr2(std::move(ptr));std::cout << *ptr2 << std::endl;return 0;
}

3. std::weak_ptr:

先观察下面的代码,如果我们在类father中使用的是shared_ptr son的话,father和son的实例并没有顺利回收,输出如下:

father !
son !

以上问题就是shared_ptr的环形引用问题。为了避免shared_ptr的环形引用问题,需要引入一个弱引用weak_ptr, weak_ptr是为了配合shared_ptr而引入的一种智能指针,弱引用不会引起引用计数增加,它更像是shared_ptr的一个助手而不是智能指针,因为它不具有普通指针的行为,没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况.

#include <iostream>
#include <memory>
using namespace std;class father;
class son;class father {public:father() {cout << "father !" << endl;}~father() {cout << "~~~~~father !" << endl;}void setSon(shared_ptr<son> s) {son = s;}
private://shared_ptr<son> son;weak_ptr<son> son; // 用weak_ptr来替换
};class son {public:son() {cout << "son !" << endl;}~son() {cout << "~~~~~~son !" << endl;}void setFather(shared_ptr<father> f) {father = f;}
private:shared_ptr<father> father;
};void test() {shared_ptr<father> f(new father());shared_ptr<son> s(new son());f->setSon(s);s->setFather(f);
}int main()
{test();return 0;
}

输出:

father !
son !
~~~~~~son !
~~~~~father !

15. 右值引用&线程库

点我

C++98C++11的区别相关推荐

  1. mysql int(3)与int(11)的区别详解

    这篇文章主要介绍了mysql int(3)与int(11)的区别详解的相关资料,需要的朋友可以参考下 mysql int(3)与int(11)的区别 总结,int(M) zerofill,加上zero ...

  2. 建议11: 区别对待==和Equals

    建议11: 区别对待==和Equals在开始本建议之前,首先要明确概念"相等性".CLR中将"相等性"分为两类:"值相等性"和"引 ...

  3. mysql int(3)与int(11)的区别

    总结,int(M) zerofill,加上zerofill后M才表现出有点点效果,比如 int(3) zerofill,你插入到数据库里的是10,则实际插入为010,也就是在前面补充加了一个0.如果i ...

  4. DirectX9 10 11对比区别摘抄整理

    dx8使得可编程的硬件进入管线成为了双重构造.对于DirectX 9的顶点处理与像素处理,则被真正的可编程处理器调换.而在向DirectX 10的转移为了实现更灵活的可编程性,需要GPU架构进行根本的 ...

  5. mysql 数据库中 int(3) 和 int(11) 什么区别?

    原文链接   原文链接 问题:int(3) 和 int(11) 有什么区别? 参看 MySQL 手册  代码如下 复制代码 int(M): M indicates the maximum displa ...

  6. p30p鸿蒙11,p30p30pro区别

    华为发布了全新的P30手机,那么P30与P30Pro之间有什么区别呢?下面就为大家带来相关的介绍. p30p30pro区别 1.P30和P30 Pro在机身尺寸上有着区别,P30 Pro机身更大尺寸为 ...

  7. 华为nova8和小米11 参数对比哪个好 华为nova8和小米11的区别

    华为nova8:为用户提供6.57英寸的OLED屏幕,为用户带来屏下指纹和面部解锁功能,为用户带来90Hz的屏幕刷新 华为手机爆降800这活动太给力了 机会不容错过 https://www.huawe ...

  8. 专硕与学硕的11个区别,不纠结!

    第一,培养目标不同 专业硕士教育的突出特点是学术型与职业性紧密结合,获得专业学位的人,主要不是从事学术研究,而是从事具有明显职业背景的工作,如工程师.医师.教师.律师.会计师等.这是一种以专业实践为导 ...

  9. 双11 iPhone再降价:24期免息 四舍五入等于不要钱!

    11月8日消息,苹果官方宣布旗下的iPhone将参加天猫双十一狂欢节,加入到平台补贴中来,时间持续四天11月8日到11月11日,四天特价官方全网最低. 据悉,苹果低价策略始于去年9月13日发布的iPh ...

最新文章

  1. 发现了阿里云 APP 的一个小 BUG
  2. vue中的nextTick
  3. 白鹭引擎制作滚动框,类似div的oveflow
  4. 计算机系统-电路设计09-计数器的内部电路实现
  5. 用DSA或者RSA进行加密或者数字签名
  6. java生成excel图表
  7. 年末总结 | 音视频开发进阶 2021 干货合集
  8. vdbench多主机运行指导
  9. Typora下载与安装详解
  10. Dev C++ 源文件编译时,确实没问题,但是运行时,总是提示源文件 未编译
  11. 全球及中国再生纸包装行业研究及十四五规划分析报告
  12. HTML结构及常用的标签
  13. java正则表达式验证标点符号
  14. 阿里面试官内部题库,阿里发布2022年Java岗(正式版)面试题
  15. Win10家庭版中文用户名改英文用户名方法(亲测管用)
  16. 为什么有些编程程序员需要两个显示器?
  17. XP系统启动时滚动条总是时间很长
  18. 助力全球抗疫,腾讯加入Linux基金会公共卫生计划
  19. vue 给iframe设置src_vue 中引入iframe,动态设置其src,遇到的一些小问题总结
  20. HashMap、哈希表、哈希函数

热门文章

  1. Linux—系统文件和目录介绍
  2. 解决针对ubuntu11.04安装中文包后不能正常查看或使用pdf和Archiver的问题
  3. 《挖掘管理价值:企业软件项目管理实战》一2.4 软件设计过程
  4. RPM   YUM
  5. 使用redis来实现分布式锁
  6. 多网卡无法上外网的解决
  7. iOS中AVFoundation的简单使用—音乐的播放
  8. Linux之物理页面的分配
  9. 几个支持生成Python代码的UML工具
  10. XWork ParameterInterceptor类绕过安全限制漏洞-解决1