C++17完整导引-模板特性之编译器的if语句

这篇具有很好参考价值的文章主要介绍了C++17完整导引-模板特性之编译器的if语句。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

通过使用语法if constexpr(...)编译器可以计算编译期的条件表达式来在编译期决定使用一个if语句的 then 的部分还是 else 的部分。其余部分的代码将会被丢弃这意味着它们甚至不会被生成。然而这并不意味着被丢弃的部分完全被忽略,这些部分中的代码也会像没使用的模板一样进行语法检查。

例如:

#include <string>

template <typename T>
std::string asString(T x)
{
    if constexpr(std::is_same_v<T, std::string>) {
        return x;                   // 如果T不能自动转换为string该语句将无效
    }
    else if constexpr(std::is_arithmetic_v<T>) {
        return std::to_string(x);   // 如果T不是数字类型该语句将无效
    }
    else {
        return std::string(x);      // 如果不能转换为string该语句将无效
    }
}

通过使用if constexpr我们在编译期就可以决定我们是简单返回传入的字符串、对传入的数字调用to_string()还是使用构造函数来把传入的参数转换为std::string。无效的调用将被 丢弃 ,因此下面的代码能够通过编译(如果使用运行时if语句则不能通过编译):

#include <iostream>

int main()
{
    std::cout << asString(42) << '\n';
    std::cout << asString(std::string("hello")) << '\n';
    std::cout << asString("hello") << '\n';
}

预编译代码如下:

//代码std::cout << asString(42) << '\n';的预编译代码如下:
/* First instantiated from: insights.cpp:19 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char, std::char_traits<char>, std::allocator<char> > asString<int>(int x)
{
  if constexpr(false) {
  } else /* constexpr */ {
    if constexpr(true) {
      return std::to_string(x);
    } 
    
  } 
  
}
#endif

//代码std::cout << asString(std::string("hello")) << '\n';的预编译代码如下:
/* First instantiated from: insights.cpp:20 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char, std::char_traits<char>, std::allocator<char> > asString<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >(std::basic_string<char, std::char_traits<char>, std::allocator<char> > x)
{
  if constexpr(true) {
    return std::basic_string<char, std::char_traits<char>, std::allocator<char> >(static_cast<std::basic_string<char, std::char_traits<char>, std::allocator<char> > &&>(x));
  } 
}
#endif

//代码std::cout << asString("hello") << '\n';的预编译代码如下:
/* First instantiated from: insights.cpp:21 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char, std::char_traits<char>, std::allocator<char> > asString<const char *>(const char * x)
{
  if constexpr(false) {
  } else /* constexpr */ {
    if constexpr(false) {
    } else /* constexpr */ {
      return std::basic_string<char, std::char_traits<char>, std::allocator<char> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> >(x, std::allocator<char>()));
    } 
  } 
}
#endif


int main()
{
  std::operator<<(std::operator<<(std::cout, asString(42)), '\n');
  std::operator<<(std::operator<<(std::cout, asString(std::basic_string<char, std::char_traits<char>, 	   std::allocator<char> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> >("hello", std::allocator<char>())))), '\n');
  std::operator<<(std::operator<<(std::cout, asString("hello")), '\n');
  return 0;
}

编译期if语句

如果我们在上面的例子中使用运行时if,下面的代码将永远不能通过编译

#include <string>

template <typename T>
std::string asString(T x)
{
    if (std::is_same_v<T, std::string>) {
        return x;                   // 如果不能自动转换为string会导致ERROR
    }
    else if (std::is_numeric_v<T>) {
        return std::to_string(x);   // 如果不是数字将导致ERROR
    }
    else {
        return std::string(x);      // 如果不能转换为string将导致ERROR
    }
}

这是因为模板在实例化时整个模板会作为一个整体进行编译。然而if语句的条件表达式的检查是运行时特性。即使在编译期就能确定条件表达式的值一定是falsethen 的部分也必须能通过编译。因此,当传递一个std::string或者字符串字面量时,会因为std::to_string()无效而导致编译失败。此外,当传递一个数字值时,将会因为第一个和第三个返回语句无效而导致编译失败。

使用编译期if语句时, then 部分和 else 部分中不可能被用到的部分将成为 丢弃的语句

  • 当传递一个std::string时,第一个if语句的 else 部分将被丢弃。
template<>
std::basic_string<char, std::char_traits<char>, std::allocator<char> > asString<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >(std::basic_string<char, std::char_traits<char>, std::allocator<char> > x)
{
  if constexpr(true) {
    return std::basic_string<char, std::char_traits<char>, std::allocator<char> >(static_cast<std::basic_string<char, std::char_traits<char>, std::allocator<char> > &&>(x));
  } 
  
}
  • 当传递一个数字时,第一个if语句的 then 部分和最后的 else 部分将被丢弃。
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char, std::char_traits<char>, std::allocator<char> > asString<int>(int x)
{
  if constexpr(false) {
  } else /* constexpr */ {
    if constexpr(true) {
      return std::to_string(x);
    } 
  } 
}
#endif
  • 当传递一个字符串字面量(类型为const char*)时,第一和第二个if语句的 then 部分将被丢弃。
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char, std::char_traits<char>, std::allocator<char> > asString<const char *>(const char * x)
{
  if constexpr(false) {
  } else /* constexpr */ {
    if constexpr(false) {
    } else /* constexpr */ {
      return std::basic_string<char, std::char_traits<char>, std::allocator<char> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> >(x, std::allocator<char>()));
    } 
  } 
}
#endif

因此,在每一个实例化中,无效的分支都会在编译时被丢弃,所以代码能成功编译。注意被丢弃的语句并不是被忽略了。即使是被忽略的语句也必须符合正确的语法,并且所有和模板参数无关的调用也必须正确。事实上,模板编译的第一个阶段( 定义期间 )将会检查语法和所有与模板无关的名称是否有效。所有的static_asserts也必须有效,即使所在的分支没有被编译。

例如:

template<typename T>
void foo(T t)
{
    if constexpr(std::is_integral_v<T>) {
        if (t > 0) {
            foo(t-1);   // OK
        }
    }
    else {
        undeclared(t); // 如果未被声明且未被丢弃将导致错误
        undeclared();  // 如果未声明将导致错误(即使被丢弃也一样)
        static_assert(false, "no integral"); // 总是会进行断言(即使被丢弃也一样)
    }
}

对于一个符合标准的编译器来说,上面的例子 永远 不能通过编译,报错如下:

<source>:17:9: error: there are no arguments to 'undeclared' that depend on a template parameter, so a declaration of 'undeclared' must be available [-fpermissive]
   17 |         undeclared();  // 如果未声明将导致错误(即使被丢弃也一样)
      |         ^~~~~~~~~~
<source>:17:9: note: (if you use '-fpermissive', G++ will accept your code, but allowing the use of an undeclared name is deprecated)
<source>:18:23: error: static assertion failed: no integral
   18 |         static_assert(false, "no integral"); // 总是会进行断言(即使被丢弃也一样)

原因有两个

  • 即使T是一个整数类型,如下调用:
undeclared(); // 如果未声明将导致错误(即使被丢弃也一样)

如果该函数未定义时即使处于被丢弃的 else 部分也会导致错误,因为这个调用并不依赖于模板参数

  • 如下断言
static_assert(false, "no integral"); // 总是会进行断言(即使被丢弃也一样)

即使处于被丢弃的 else 部分也总是会断言失败,因为它也不依赖于模板参数。一个使用编译期条件的静态断言没有这个问题:

static_assert(!std::is_integral_v<T>, "no integral");

注意有一些编译器(例如Visual C++ 20132015)并没有正确实现模板编译的两个阶段。它们把第一个阶段( 定义期间 )的大部分工作推迟到了第二个阶段( 实例化期间 ),因此有些无效的函数调用甚至一些错误的语法都可能通过编译

使用编译期if语句

理论上讲,只要条件表达式是编译期的表达式你就可以像使用运行期if一样使用编译期if。你也可以混合使用编译期和运行期的if

if constexpr (std::is_integral_v<std::remove_reference_t<T>>) {
    if (val > 10) {
        if constexpr (std::numeric_limits<char>::is_signed) {
            ...
        }
        else {
            ...
        }
    }
    else {
        ...
    }
}
else {
    ...
}

注意你不能在函数体之外使用if constexpr。因此,你不能使用它来替换预处理器的条件编译。

编译期if的注意事项

使用编译期if可能会导致一些并不明显的后果

编译期if影响返回值类型

编译期if可能会影响函数的返回值类型。例如,下面的代码总能通过编译,但返回值的类型可能会不同:

auto foo()
{
    if constexpr (sizeof(int) > 4) {
        return 42;
    }
    else {
        return 42u;
    }
}

这里,因为我们使用了auto,返回值的类型将依赖于返回语句,而执行哪条返回语句又依赖于int的字节数:

  • 如果大于4字节,返回42的返回语句将会生效,因此返回值类型是int
  • 否则,返回42u的返回语句将生效,因此返回值类型是unsigned int
    预处理代码如下:
unsigned int foo()
{
  if constexpr(false) {
    return 42;
  } else /* constexpr */ {
    return 42U;
  } 
  
}

这种情况下有if constexpr语句的函数可能返回完全不同的类型。例如,如果我们不写 else 部分,返回值将会是int或者void

auto foo()  // 返回值类型可能是int或者void
{
    if constexpr (sizeof(int) > 4) {
        return 42;
    }
}

预处理代码如下:

void foo()
{
  if constexpr(false) {
    return 42;
  } 
  
}
//或者
int foo()
{
  if constexpr(true) {
    return 42;
  } 
  
}

注意这里如果使用运行期if那么代码将永远不能通过编译,因为推导返回值类型时会考虑到所有可能的返回值类型,因此推导会有歧义
例如

#include <iostream>
#include <string>
#include <utility>

using namespace std;

auto foo() {
    if (sizeof(int) > 4) {
        return 42;
    } else {
        return 42u;
    }
}

int main() { return 0; }

会有如下报错:

<source>:13:16: error: inconsistent deduction for auto return type: 'int' and then 'unsigned int'
   13 |         return 42u;
      |                ^~~
即使在 then 部分返回也要考虑 else 部分

运行期if有一个模式不能应用于编译期if:如果代码在 thenelse 部分都会返回,那么在运行期if中你可以跳过else部分。也就是说,

if (...) {
    return a;
}
else {
    return b;
}

可以写成:

if (...) {
    return a;
}
return b;

但这个模式不能应用于编译期if,因为在第二种写法里,返回值类型将同时依赖于两个返回语句而不是依赖其中一个,这会导致行为发生改变。例如,如果按照上面的示例修改代码,那么 也许能也许不能 通过编译:

auto foo()
{
    if constexpr (sizeof(int) > 4) {
        return 42;
    }
    return 42u;
}

如果条件表达式为true(int大于4字节),编译器将会推导出两个不同的返回值类型,这会导致错误。否则,将只会有一条有效的返回语句,因此代码能通过编译。
可能的报错如下:

<source>:11:12: error: inconsistent deduction for auto return type: 'int' and then 'unsigned int'
   11 |     return 42u;
      |            ^~~
编译期短路求值

考虑如下代码:

template<typename T>
constexpr auto foo(const T& val)
{
    if constexpr(std::is_integral<T>::value) {
        if constexpr (T{} < 10) {
            return val * 2;
        }
    }
    return val;
}

这里我们使用了两个编译期条件来决定是直接返回传入的值还是返回传入值的两倍

下面的代码都能编译:

constexpr auto x1 = foo(42);    // 返回84
constexpr auto x2 = foo("hi");  // OK,返回"hi"

预处理代码如下:

#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline constexpr int foo<int>(const int & val)
{
  if constexpr(true) {
    if constexpr(true) {
      return val * 2;
    } 
    
  } 
  
  return val;
}
#endif


#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline constexpr const char * foo<char[3]>(const char (&val)[3])
{
  if constexpr(false) {
  } 
  
  return val;
}
#endif


int main()
{
  constexpr const int x1 = foo(42);
  constexpr const char *const x2 = foo("hi");
  return 0;
}

运行时if的条件表达式会进行短路求值(当&&左侧为false时停止求值,当||左侧为true时停止求值)。这可能会导致你希望编译期if也会短路求值:

template<typename T>
constexpr auto bar(const T& val)
{
    if constexpr (std::is_integral<T>::value && T{} < 10) {
        return val * 2;
    }
    return val;
}

然而,编译期if的条件表达式总是作为整体实例化并且必须整体有效,这意味着如果传递一个不能进行<10运算的类型将不能通过编译:

constexpr auto x2 = bar("hi");  // 编译期ERROR

报错信息如下:

<source>: In instantiation of 'constexpr auto bar(const T&) [with T = char [3]]':
<source>:17:28:   required from here
<source>:10:53: error: taking address of temporary array
   10 |     if constexpr (std::is_integral<T>::value && T{} < 10) {
      |                                                 ~~~~^~~~
<source>:11:20: error: invalid operands of types 'const char [3]' and 'int' to binary 'operator*'
   11 |         return val * 2;
      |                ~~~~^~~
<source>: In function 'int main()':
<source>:17:28: error: 'constexpr auto bar(const T&) [with T = char [3]]' called in a constant expression
   17 |     constexpr auto x2 = bar("hi");
      |                         ~~~^~~~~~
<source>:8:16: note: 'constexpr auto bar(const T&) [with T = char [3]]' is not usable as a 'constexpr' function because:
    8 | constexpr auto bar(const T& val)
      |         

因此,编译期if实例化时并不短路求值如果后边的条件的有效性依赖于前边的条件,那你需要把条件进行嵌套。例如,你必须写成如下形式:

if constexpr (std::is_same_v<MyType, T>) {
    if constepxr (T::i == 42) {
        ...
    }
}

而不是写成:

if constexpr (std::is_same_v<MyType, T> && T::i == 42) {
    ...
}

其他编译期if的示例

完美返回泛型值

编译期if的一个应用就是先对返回值进行一些处理,再进行完美转发。因为decltype(auto)不能推导为void(因为void不完全类型),所以你必须像下面这么写:

#include <functional>  // for std::forward()
#include <functional>  // for std::forward()
#include <iostream>
#include <type_traits>  // for std::is_same<> and std::invoke_result<>

using namespace std;

double foo1(int x, int y) { return x * 2.5 + y; }
void foo2() { cout << "foo2" << endl; }

template <typename Callable, typename... Args>
decltype(auto) call(Callable op, Args&&... args) {
    if constexpr (std::is_void_v<std::invoke_result_t<Callable, Args...>>) {
        // 返回值类型是void:
        op(std::forward<Args>(args)...);
        return;
    } else {
        // 返回值类型不是void:
        decltype(auto) ret{op(std::forward<Args>(args)...)};
        return ret;
    }
}
int main() {
    double tv1 = call(foo1, 5, 3);
    cout << tv1 << endl;
    call(foo2);
    return 0;
}

函数的返回值类型可以推导为void,但ret的声明不能推导为void,因此必须把op返回void的情况单独处理。
运行结果如下:

15.5
foo2

预编译代码如下:

#include <functional>  // for std::forward()
#include <iostream>
#include <type_traits>  // for std::is_same<> and std::invoke_result<>

using namespace std;

double foo1(int x, int y)
{
  return (static_cast<double>(x) * 2.5) + static_cast<double>(y);
}

void foo2()
{
  std::operator<<(std::cout, "foo2").operator<<(std::endl);
}


template<typename Callable, typename ... Args>
decltype(auto) call(Callable op, Args &&... args)
{
  if constexpr(std::is_void_v<std::invoke_result_t<Callable, Args...> >) {
    op(std::forward<Args>(args)... );
    return;
  } else /* constexpr */ {
    decltype(auto) ret = {op(std::forward<Args>(args)... )};
    return ret;
  } 
  
}


#ifdef INSIGHTS_USE_TEMPLATE
template<>
double call<double (*)(int, int), int, int>(double (*op)(int, int), int && __args1, int && __args2)
{
  if constexpr(false) {
  } else /* constexpr */ {
    double ret = {op(std::forward<int>(__args1), std::forward<int>(__args2))};
    return ret;
  } 
  
}
#endif


#ifdef INSIGHTS_USE_TEMPLATE
template<>
void call<void (*)()>(void (*op)())
{
  if constexpr(true) {
    op();
    return;
  } 
  
}
#endif

int main()
{
  double tv1 = call(foo1, 5, 3);
  std::cout.operator<<(tv1).operator<<(std::endl);
  call(foo2);
  return 0;
}
使用编译期if进行类型分发

编译期if的一个典型应用是类型分发。在C++17之前,你必须为每一个想处理的类型重载一个单独的函数。现在,有了编译期if,你可以把所有的逻辑放在一个函数里。

例如,如下的重载版本的std::advance()算法:

template<typename Iterator, typename Distance>
void advance(Iterator& pos, Distance n) {
    using cat = std::iterator_traits<Iterator>::iterator_category;
    advanceImpl(pos, n, cat{}); // 根据迭代器类型进行分发
}

template<typename Iterator, typename Distance>
void advanceImpl(Iterator& pos, Distance n, std::random_access_iterator_tag) {
    pos += n;
}

template<typename Iterator, typename Distance>
void advanceImpl(Iterator& pos, Distance n, std::bidirectional_iterator_tag) {
    if (n >= 0) {
        while (n--) {
            ++pos;
        }
    }
    else {
        while (n++) {
            --pos;
        }
    }
}

template<typename Iterator, typename Distance>
void advanceImpl(Iterator& pos, Distance n, std::input_iterator_tag) {
    while (n--) {
        ++pos;
    }
}

现在可以把所有实现都放在同一个函数中:

template<typename Iterator, typename Distance>
void advance(Iterator& pos, Distance n) {
    using cat = std::iterator_traits<Iterator>::iterator_category;
    if constexpr (std::is_convertible_v<cat, std::random_access_iterator_tag>) {
        pos += n;
    }
    else if constexpr (std::is_convertible_v<cat, std::bidirectional_access_iterator_tag>) {
        if (n >= 0) {
            while (n--) {
                ++pos;
            }
        }
        else {
            while (n++) {
                --pos;
            }
        }
    }
    else {  // input_iterator_tag
        while (n--) {
            ++pos;
        }
    }
}

这里我们就像是有了一个编译期switch,每一个if constexpr语句就像是一个case。然而,注意例子中的两种实现还是有一处不同的:

  • 重载函数的版本遵循 最佳匹配 语义。
  • 编译期if的版本遵循 最先匹配 语义。

另一个类型分发的例子是使用编译期if实现get<>()重载来实现结构化绑定接口
第三个例子是在用作std::variant<>访问器的泛型lambda中处理不同的类型。

带初始化的编译期if语句

注意编译期if语句也可以使用新的带初始化的形式。例如,如果有一个constexpr函数foo(),你可以这样写:

#include <functional>  // for std::forward()
#include <iostream>
#include <type_traits>  // for std::is_same<> and std::invoke_result<>

using namespace std;

double foo(int x) { return x * 2.5; }

template <typename T>
void bar(const T x) {
    if constexpr (auto obj = foo(x); std::is_same_v<decltype(obj), T>) {
        std::cout << "foo(x) yields same type\n";
    } else {
        std::cout << "foo(x) yields different type\n";
    }
}
int main() {
    bar(2);
    bar(2.5);
    return 0;
}

运行结果如下:

foo(x) yields different type
foo(x) yields same type

预处理代码如下:

#include <functional>  // for std::forward()
#include <iostream>
#include <type_traits>  // for std::is_same<> and std::invoke_result<>

using namespace std;

double foo(int x)
{
  return static_cast<double>(x) * 2.5;
}


template<typename T>
void bar(const T x)
{
  {
    auto obj = foo(x);
    if constexpr(std::is_same_v<decltype(obj), T>) {
      std::operator<<(std::cout, "foo(x) yields same type\n");
    } else /* constexpr */ {
      std::operator<<(std::cout, "foo(x) yields different type\n");
    } 
    
  }
  
}
/* First instantiated from: insights.cpp:20 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
void bar<int>(const int x)
{
  {
    double obj = foo(x);
    if constexpr(false) {
    } else /* constexpr */ {
      std::operator<<(std::cout, "foo(x) yields different type\n");
    } 
    
  }
}
#endif

/* First instantiated from: insights.cpp:21 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
void bar<double>(const double x)
{
  {
    double obj = foo(static_cast<int>(x));
    if constexpr(true) {
      std::operator<<(std::cout, "foo(x) yields same type\n");
    } 
    
  }
  
}
#endif
int main()
{
  bar(2);
  bar(2.5);
  return 0;
}

如果有一个参数类型也为Tconstexpr函数foo(),你就可以根据foo(x)是否返回与x相同的类型来进行不同的处理。
如果要根据foo(x)返回的值来进行判定,那么可以这么写:

#include <functional>  // for std::forward()
#include <iostream>
#include <type_traits>  // for std::is_same<> and std::invoke_result<>

using namespace std;

constexpr int foo(int x) { return x * 2; }

void bar() {
    constexpr auto c = 2;
    if constexpr (constexpr auto obj = foo(c); obj == 4) {
        std::cout << "foo("<<"c"<<") ==  " << obj << endl;
    } else {
        std::cout << "foo(x) yields different type\n";
    }
}
int main() {
    bar();
    return 0;
}

注意如果想在条件语句中使用obj的值,那么obj必须要声明为constexpr。运行结果如下:

foo(c) ==  4

预处理代码如下:

#include <functional>  // for std::forward()
#include <iostream>
#include <type_traits>  // for std::is_same<> and std::invoke_result<>

using namespace std;

inline constexpr int foo(int x)
{
  return x * 2;
}

void bar()
{
  constexpr const int c = 2;
  {
    constexpr const int obj = foo(c);
    if constexpr(true) {
      std::operator<<(std::operator<<(std::operator<<(std::cout, "foo("), "c"), ") ==  ").operator<<(obj).operator<<(std::endl);
    } else /* constexpr */ {
      std::operator<<(std::cout, "foo(x) yields different type\n");
    } 
  }
}
int main()
{
  bar();
  return 0;
}

在模板之外使用编译期if

if constexpr可以在任何函数中使用,而并非仅限于模板。只要条件表达式是编译期的,并且可以转换成bool类型。然而,在普通函数里使用时 thenelse 部分的所有语句都必须有效,即使有可能被丢弃

例如,下面的代码不能通过编译,因为undeclared()的调用必须是有效的,即使char是有符号数导致 else 部分被丢弃也一样:

#include <limits>

template<typename T>
void foo(T t);

int main()
{
    if constexpr(std::numeric_limits<char>::is_signed) {
        foo(42);        // OK
    }
    else {
        undeclared(42); // 未声明时总是ERROR(即使被丢弃)
    }
}

下面的代码也永远不能成功编译,因为总有一个静态断言会失败

if constexpr(std::numeric_limits<char>::is_signed) {
    static_assert(std::numeric_limits<char>::is_signed);
}
else {
    static_assert(!std::numeric_limits<char>::is_signed);
}

在泛型代码之外使用编译期if的唯一好处是被丢弃的部分不会成为最终程序的一部分,这将减小生成的可执行程序的大小。例如,在如下程序中:

#include <limits>
#include <string>
#include <array>

int main()
{
    if (!std::numeric_limits<char>::is_signed) {
        static std::array<std::string, 1000> arr1;
        ...
    }
    else {
        static std::array<std::string, 1000> arr2;
        ...
    }
}

要么arr1要么arr2会成为最终可执行程序的一部分,但不可能两者都是。

参考

http://www.cppstd17.com/文章来源地址https://www.toymoban.com/news/detail-458107.html

到了这里,关于C++17完整导引-模板特性之编译器的if语句的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • linux如何查看编译器支持的C++版本(支持C++11、支持C++14、支持C++17、支持C++20)(编译时不指定g++版本,默认使用老版本编译)

    C++11 C++11是一个重要的C++标准版本,于2011年发布。C++11带来了许多重要的改进,包括: 智能指针:引入了shared_ptr和unique_ptr等智能指针,用于更好地管理动态内存分配。 新的循环语句:引入了for循环中的范围语法,以更简洁的方式遍历容器。 初始化列表:允许使用初始化列表

    2024年02月02日
    浏览(36)
  • C++输出编译器名称和版本以及编译器位数、C/C++常见编译器

    常见的C/C++编译器主要包括以下几种: GCC (GNU Compiler Collection):GCC是一个广泛使用的编译器套件,支持多种编程语言,包括C、C++、Objective-C等。它具有强大的优化能力和跨平台支持,并且被广泛应用于各种操作系统和开发环境。 Clang :Clang是基于LLVM的编译器前端,支持C、

    2024年02月13日
    浏览(44)
  • python在线编译器搭建,python在线编译器源码

    本篇文章给大家谈谈python在线编译器搭建,以及python在线编译器源码,希望对各位有所帮助,不要忘了收藏本站喔。 1. PyCharm集成开发环境 2. PyCharm的下载与安装 3. Pycharm的使用 3.1 创建Python项目 3.2 创建子目录 3.3 创建Python文件 3.4 切换解释器 3.5 常用快捷键 4. Pycharm常用配置

    2024年03月25日
    浏览(59)
  • 编译器(Compiler)及C/C++编译器安装(c+安装)

    目录 一、常用编程语言的编译器(compiler) 概述 二、GCC、MinGW、MinGW-w64 、TDM-GCC、Cygwin、MSYS、MSYS2的区别 三、MinGW-w64编译器套件下载及安装 四、MinGW-w64安装后,windows环境变量配置(设置) 五、编译器的运行及其与开发环境的关系、编译器的来源        机器语言是一种计算机指

    2024年02月07日
    浏览(66)
  • 探索Kotlin K2编译器和Java编译器的功能和能力

    文章首发地址 Kotlin K2编译器是Kotlin语言的编译器,负责将Kotlin源代码转换为Java字节码或者其他目标平台的代码。K2编译器是Kotlin语言的核心组件之一,它的主要功能是将Kotlin代码编译为可在JVM上运行的字节码。 编译过程: Kotlin K2编译器将Kotlin源代码作为输入,并经过词法分

    2024年02月11日
    浏览(46)
  • 【C语言】--编译及编译器

    夫学须静也,才须学也;非学无以广才,非志无以成学 个人主页:【😊个人主页】 系列专栏:【❤️系列专栏】 C语言一直以来都是初入编程的小白们的必修课,作为程序员必学语言之一,C语言自然有属于它的奥秘,接下来就由我来带领大家走进C语言的世界吧🚗🚗🚗 1、

    2024年02月13日
    浏览(54)
  • openharmony 编译LLVM编译器基础架构

    third_party_llvm-project: 管理员 liwentao_uiw dhy308 huanghuijin (1) 缺少依赖,一次安装好几个依赖 (2) case in的语法识别不了 实际上case in是没有问题的,主要是结尾需要改成Unix结尾

    2024年01月19日
    浏览(44)
  • 编译原理课程设计--C语言编译器

    源程序1: 源程序1词法分析结果: 与程序1语法分析结果(部分) 源程序1四元式: 源程序1优化后的四元式: action-goto表(部分) 文件目录: (1)掌握语义分析过程,即语法制导翻译过程。 (2)在语法分析的LR分析程序中的基础上添加程序,进行语义分析,生成源程序的四

    2024年02月08日
    浏览(56)
  • 【Linux工具】编译器、调式器、项目自动化构建工具以及git的使用(1编译器)

    作者:爱写代码的刚子 时间:2023.6.3 本篇博客主要详细介绍Linux中十分重要的工具:编译器,灵活使用这些工具是Linux中一项必备技能。项目自动化构建工具、调式器、git工具会在下一篇博客中进行介绍。 Linux编译器-gcc/g++使用 gcc编译C语言: g++编译C++: gcc只能编译C语言,而

    2024年02月09日
    浏览(66)
  • 交叉编译器介绍

    简介 ​ 要在 X86 的电脑上编译出能够在 Arm 上运行的程序,我们必须明确告诉编译器,编译生成的可执行文件需要以 Arm 指令集的标准编码。开发者们为不同的芯片开发了不同的编译器,比如针对 Arm 平台的 arm-linux-gcc,针对 mips 平台的 mips-linux-gnu-gcc,这些编译器都是基于 G

    2024年02月06日
    浏览(58)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包