Learning C++ 之1.7 提前定义和声明

看看下面这个看似好用的示例程序:

#include <iostream>
 
int main()
{
    std::cout << "The sum of 3 and 4 is: " << add(3, 4) << std::endl;
    return 0;
}
 
int add(int x, int y)
{
    return x + y;
}

你可能期望这个程序输出:

The sum of 3 and 4 is:7

事实上这个是不对的,在编译的时候就上报了错误。

add.cpp(5) : error C3861: 'add': identifier not found
add.cpp(9) : error C2365: 'add' : redefinition; previous definition was 'formerly unknown identifier'

这个程序之所以编译失败就是因为程序是从上到下依次执行的,main函数中第一次使用add函数的时候根本不知道add是什么意思。因为add的第一次定义在第九行。这就导致了编译错误。

到了第九行碰到第一次定义的时候,编译器还会报错,这次上报了重复定义的错误。这个有点误导性,纠错的时候,我们需要从上到下一条一条来进行。

为了解决这个问题,我们会修改一下让add函数在被调用的时候知道他的定义。有两种方法来改正这个错误:

1.把add函数添加到main函数的前面,这样main函数在调用的时候就知道add的定义了。

#include <iostream>
 
int add(int x, int y)
{
    return x + y;
}
 
int main()
{
    std::cout << "The sum of 3 and 4 is: " << add(3, 4) << std::endl;
    return 0;
}

当然这种方法并不总是可行的,比如有两个函数A和B,A调用了B,同时B也调用了A,这种方法就无法解决这个问题了。因此需要下面的方法。

函数原型和提前声明

2.用一个提前的声明量

提前的声明能够在我们定义标识符之前让我们告诉编译器标识符的存在。

在函数中的具体体现就是,当我们还没有真正定义函数的时候,先告诉编译器这个函数的存在。当编译器碰到这个函数的声明的时候,他会知道我们已经定义了这个函数,然后再检查我们是否可以正确地调用这个函数,即使他并不知道这个函数在哪里定义的。

为了定义一个函数的声明,我们用到了函数原型的概念。函数原型包含了函数的名字,返回值,参数,但是不包括函数的实体。因为函数的声明是一个语句,因此必须以;结尾。

下面是函数的声明:

int add(int x, int y); // function prototype includes return type, name, parameters, and semicolon.  No function body!

下面是我们之前没有编译通过的程序,增加了一条函数声明:

#include <iostream>
 
int add(int , int);

// forward declaration of add() (using a function prototype) int main(){ std::cout << "The sum of 3 and 4 is: " << add(3, 4) << std::endl; // this works because we forward declared add() above return 0;} int add(int x, int y) // even though the body of add() isn't defined until here{ return x + y;}

现在当main函数第一次调用到add的时候,他明白了add是一个怎样的函数(返回值是int,两个int参数,函数名字是add)。现在就不会报错了。

当然函数的声明没有必要声明函数参数的名字,下面的定义也是可以的:

int add(int , int );

当然还是建议大家写上参数的名字,因为这样可以明确的知道函数的参数名称以及干什么用的。要不然的话,你得去查找真正的函数定义的地方。

忘掉函数体:

有一个问题是假如我们把函数体的定义给忘掉了,会发生什么事情呢。

这个要分情况而定。假如这个声明的函数在具体的函数中没有用到,那么就没有问题。但是假如在函数中调用了这个函数,编译的时候是没有问题的,但是链接的时候,由于找不到该函数的函数体,就会报错。

#include <iostream>
 
int add(int x, int y); // forward declaration of add() using function prototype
 
int main()
{
    std::cout << "The sum of 3 and 4 is: " << add(3, 4) << std::endl;
    return 0;
}

上面的函数就会报下面的错误:

Compiling...
add.cpp
Linking...
add.obj : error LNK2001: unresolved external symbol "int __cdecl add(int,int)" (?add@@YAHHH@Z)
add.exe : fatal error LNK1120: 1 unresolved externals

其他类型的提前声明:

提前声明通常用在函数里,但是在C++中的其它声明符也可以使用提前声明的方法,向变量和用户自己定义的类型。其他类型的提前声明有不同的语法。

这些我们会在后续的课程中深入学习。

声明和定义:

在C++里你常常听到如下的“定义”和“声明”。这个具体的区别是什么?你现在已经有了足够的架构来理解这两个词语了。

定义实际上就是实现和实例化具体的标识符(分配内存)。下面是一些定义的例子:

int add(int x, int y) // implements function add()
{
    return x + y;
}
 
int x; // instantiates (causes memory to be allocated for) an integer variable named x

定义需要满足连接器,如果用一个标识符但是没有定义,那么链接器就会报错。

对于一个标识符来说,你只可以定义一次。这就称做一次性定义原则,也叫ODR。在大多数情况下多次定义同一个标识符是错误的,即使他们可能是相同的。

一个声明只是告诉编译器存在这个标识符和它的类型。下面是一些声明的例子:

int add(int x, int y); // tells the compiler about a function named "add" that takes two int parameters and returns an int.  No body!
int x; // tells the compiler about an integer variable named x

声明只需要满足编译器就可以了,如果你使用一个标识符直接定义,没有声明,那么就会上报编译的错误。

你可以看到int x;来说都是满足定义和声明的,这是因为在C++中,所有的定义也可以当作声明。int x;是一个定义的话,他当然也算是一个声明。因此在大多数情况下我们只需要一个定义就可以了,但是一定要在定义之前,提供一个标识符一个详细的声明。

当然有一些声明并不是定义,比如函数原型。这种被称作纯声明。其他类型的纯声明,如变量声明,类声明,类型声明。你可以想你期望的那样,对标识符有很多纯声明,当然除了一个意外,其他的纯声明都是无效的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个简单的Q-learning算法的C++代码示例,帮助你入门: ``` #include <iostream> #include <cmath> #include <ctime> #include <cstdlib> using namespace std; const int NUM_STATES = 6; // 状态数量 const int NUM_ACTIONS = 2; // 动作数量 const double GAMMA = 0.8; // 折扣因子 const double ALPHA = 0.1; // 学习率 const int MAX_EPISODES = 13; // 最大迭代次数 int R[NUM_STATES][NUM_ACTIONS] = { {0, 0}, {0, 100}, {0, 0}, {100, 0}, {0, 0}, {0, 0} }; // 奖励矩阵 int Q[NUM_STATES][NUM_ACTIONS] = {0}; // Q值矩阵 int choose_action(int state) { // 选择动作 int action; if (rand() / (double)RAND_MAX < 0.8) { // 以80%的概率选择Q值最大的动作 int maxQ = -1; for (int i = 0; i < NUM_ACTIONS; i++) { if (Q[state][i] > maxQ) { maxQ = Q[state][i]; action = i; } } } else { // 以20%的概率随机选择动作 action = rand() % NUM_ACTIONS; } return action; } void q_learning() { // Q-learning算法 for (int i = 0; i < MAX_EPISODES; i++) { int state = rand() % NUM_STATES; while (state != 5) { // 当状态不是终止状态时 int action = choose_action(state); int next_state; if (action == 0) { next_state = state - 1; } else { next_state = state + 1; } int reward = R[state][action]; Q[state][action] = (1 - ALPHA) * Q[state][action] + ALPHA * (reward + GAMMA * max(Q[next_state][0], Q[next_state][1])); // 更新Q值 state = next_state; } } } int main() { srand(time(NULL)); q_learning(); cout << "Q values:" << endl; for (int i = 0; i < NUM_STATES; i++) { for (int j = 0; j < NUM_ACTIONS; j++) { cout << Q[i][j] << " "; } cout << endl; } return 0; } ``` 以上代码实现了一个简单的Q-learning算法,用于解决一个简单的迷宫问题。其中,R矩阵为奖励矩阵,Q矩阵为Q值矩阵,choose_action函数用于选择动作,q_learning函数用于执行Q-learning算法。在main函数中,我们调用q_learning函数,然后打印出Q值矩阵的值。 请注意,此示例程序仅用于演示Q-learning的基本思想,实际应用中需要根据具体问题进行调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值