空间复杂度为1的从右向左显示


    public void doTest(View view){
        String TAG = "kodulf";

//        请给出算法,要求空间复杂度为1
//        部分国家是从右向做阅读的,要求给出算法,将下面的message 转化为从右向左的
//        例如:"hello world" 就转为 "world hello"
//        例如:"are you ok" 就转为"ok you are"

        char[] message = {'h','e','l','l','o',' ','w','o','r','l','d'};
        char[] message2 = {'a','r','e',' ','y','o','u',' ','o','k'};


        test(message);
        Log.d(TAG,"message :" + new String(message));


        test(message2);
        Log.d(TAG,"message2 :" + new String(message2));
    }

    public void test(char[] message){
        String TAG = "kodulf";
        int length = message.length;
        //1:先整体调换
        reverse(message,0,message.length-1);

        int start = 0;

        for (int i = 0; i < message.length; i++) {
            if (message[i]== ' '){
                Log.d(TAG,"message-mid : " + "start = " +start + " i = " + i);
                reverse(message,start,i-1);
                Log.d(TAG,"message-mid :" + new String(message) + "start = " +start);
                start = i+1;
                Log.d(TAG,"message-mid :" + "start = " +start);
            }
        }
    }

    public void reverse(char[] message, int start, int end){
        String TAG = "kodulf";
        Log.d(TAG,"reverse :" + "start = " +start + " end =  "+ end + " message = " +new String(message));
        char temp;
        for (int i = start; i <=((start+end)/2); i++) {
            temp = message[i];
            //这里这一步很重要就是要+start,否则中间的一些单纯就会有问题,因为例如3,5之间,如果直接end-i就是5-3 = 2了。
            message[i] = message[end -i + start];
            message[end -i + start] = temp;
        }
    }

豆包给出的:

public class ReverseWordsInArray {
    public static void reverseWords(char[] message) {
        // 先整体反转字符数组
        reverse(message, 0, message.length - 1);

        int start = 0;
        int end;
        // 再逐个反转每个单词
        while (start < message.length) {
            end = start;
            while (end < message.length && message[end]!= ' ') {
                end++;
            }
            reverse(message, start, end - 1);
            start = end + 1;
        }
    }

    private static void reverse(char[] arr, int start, int end) {
        while (start < end) {
            char temp = arr[start];
            arr[start] = arr[end];
            arr[end] = temp;
            start++;
            end--;
        }
    }

    public static void main(String[] args) {
        char[] message1 = {'h','e','l','l','o',' ','w','o','r','l','d'};
        reverseWords(message1);
        System.out.println(new String(message1));

        char[] message2 = {'a','r','e',' ','y','o','u',' ','o','k'};
        reverseWords(message2);
        System.out.println(new String(message2));
    }
}
这个算法首先整体反转整个字符数组,然后逐个反转每个单词,从而实现将字符串从左向右的顺序转换为从右向左的顺序,且空间复杂度为 O (1),只使用了有限的额外变量,不随输入规模的增加而增加额外的空间需求。

<think> 我们正在讨论函数参数压栈顺序的问题。根据引用[1]和引用[2],C语言中函数参数压栈顺序从右向左的原因主要与编译器的实现有关。具体来说,从右向左压栈可以保证在生成汇编代码时,函数参数相对于栈帧基址(如BP寄存器)的偏移量是固定的,这样无论用户传入的参数数量多少,第一个参数的位置总是确定的。这大大简化了编译器的设计。 引用[1]指出,如果从左向右压栈,那么第一个参数的位置就会根据传入的参数总数而变化,这样编译器在访问参数时就需要动态计算偏移量,增加了编译器的复杂性。而从右向左压栈,第一个参数总是位于栈顶的固定位置(在栈帧基址的上方一个固定偏移处),这样编译器可以很容易地访问每个参数。 另外,引用[2]和引用[3]提到了一个例子:printf("%d,%d", a, ++a); 的输出结果是5,5。这个例子也说明了参数计算顺序是从右向左的。因为先计算了++a(将a变为5),然后再取a的值(此时a已经是5),所以两个%d都输出5。这进一步印证了参数压栈顺序是从右向左的。 总结一下,参数从右往左压栈的原因: 1. 固定偏移量:保证函数参数相对于栈帧基址的偏移量固定,便于编译器访问参数。 2. 简化编译器:避免因参数数量变化而导致第一个参数位置变化,降低编译器设计的复杂度。 注意:虽然压栈顺序是从右向左,但这并不改变函数调用时参数的计算顺序(C语言标准未规定参数计算顺序,但大多数编译器实现为从右向左计算,与压栈顺序一致)。 下面我们用汇编代码来举例说明: 假设有一个函数调用:func(a, b, c); 按照从右向左压栈: push c push b push a 在栈中的布局如下(假设栈从高地址向低地址增长): ... [a] <- 栈顶(低地址) [b] [c] <- 调用func之前,栈顶指向a的位置 进入函数func后,栈帧建立,通常会有: push %ebp ; 保存旧的基址指针 mov %esp, %ebp ; 新的基址指针指向当前栈顶 此时,在函数内部,通过ebp可以访问参数: [ebp+8] -> a (第一个参数) [ebp+12] -> b (第二个参数) [ebp+16] -> c (第三个参数) 可以看到,第一个参数a的地址是ebp+8,这个偏移量是固定的,与参数个数无关。这就是从右向左压栈带来的好处。 而如果从左向右压栈(先压a,再压b,最后压c),那么栈布局将是: [c] <- 栈顶(低地址) [b] [a] 在函数内部,通过ebp访问参数时,第一个参数a的地址将是ebp+(参数总长度)+8(因为要跳过c和b才能到a),这样就需要知道参数个数才能确定a的位置,增加了访问的复杂度。 因此,采用从右向左压栈,使得函数内部访问参数的方式统一且简单,无论函数有多少个参数,第一个参数总是在ebp+8的位置。 另外,关于可变参数函数(如printf),从右向左压栈还有一个好处:第一个参数(格式字符串)最后被压入栈,因此它位于栈顶附近,函数可以很容易地找到它,然后根据格式字符串中的格式说明符来确定后面还有多少个参数以及它们的类型。 最后,引用[4]中的例子也说明了参数计算顺序与压栈顺序一致(从右向左),这导致表达式求值顺序也是从右向左,因此需要注意表达式的副作用(如自增操作)。 综上所述,参数从右往左压栈主要是为了编译器实现的方便,特别是为了固定每个参数在栈帧中的位置,使得函数内部访问参数变得简单直接。 相关问题: 1. 除了从右向左压栈,还有哪些常见的函数调用约定? 2. 在x86-64架构下,函数参数传递方式与32位x86有何不同? 3. 可变参数函数(如printf)的实现原理是什么? 4. 参数计算顺序和压栈顺序是否总是相同的?为什么? </think> ### 函数参数从右向左压栈的原因分析 参数压栈采用**从右向左**的顺序(如 `printf("%d,%d", a, ++a)` 先处理 `++a` 再处理 `a`),主要基于以下核心原因: #### 1. **简化编译器实现(核心原因)** - **固定参数偏移量**: 从右向左压栈时,第一个参数始终位于栈顶固定位置(`$ebp+8$`)。无论函数有多少参数,**第一个参数的地址恒定不变**: $$ \text{参数地址} = \begin{cases} \text{第1个参数} & \rightarrow ebp+8 \\ \text{第2个参数} & \rightarrow ebp+12 \\ \vdots & \vdots \\ \text{第n个参数} & \rightarrow ebp+8+4(n-1) \end{cases} $$ 若从左向右压栈,第一个参数的偏移量会随参数数量变化,编译器需动态计算偏移,复杂度显著增加[^1]。 - **支持可变参数函数**: 如 `printf` 需通过首个参数(格式字符串)确定后续参数数量。从右向左压栈后,栈顶即为格式字符串地址,可直接定位: ```c printf("%d %f %s", a, b, c); // 压栈顺序:c → b → a → "%d %f %s" ``` 此时 `printf` 通过 `$ebp+8$` 直接读取格式字符串,再按需向下遍历栈。 #### 2. **表达式求值顺序一致性** - **计算与压栈顺序统一**: 参数**计算顺序与压栈顺序均为从右向左**(如 `func(a++, ++a)`): 1. 先计算最右侧 `++a` → 结果暂存寄存器 2. 再计算左侧 `a++` → 结果暂存栈空间 3. 压栈时直接复用已计算结果[^3] ```nasm ; 示例:func(a++, ++a) inc dword [a] ; 先计算 ++a → 结果存寄存器 mov ebx, [a] ; push ebx ; 压栈 ++a 的结果 mov ecx, [a] ; 再取 a 值(此时a已自增) push ecx ; 压栈 a++ 的原始值 inc dword [a] ; 后执行 a++ 自增 ``` #### 3. **性能优化** - **减少指令生成**: 从右向左压栈允许编译器复用已计算的中间结果(如寄存器暂存值),避免重复计算[^3]。 - **栈操作高效性**: 压栈指令 `push` 隐含 `esp` 递减操作,从右向左压栈时无需额外调整栈指针位置。 #### 对比实验分析 ```c #include <stdio.h> void main() { int a=4; printf("%d,%d", a, ++a); // 输出 5,5 [^4] } ``` **执行过程**: 1. 先计算 `++a` → `a=5` → 结果存寄存器 2. 再读取 `a` 当前值(5) 3. 压栈顺序:`5`(对应 `a`)→ `5`(对应 `++a`)→ `"%d,%d"` 4. `printf` 从栈顶依次读取:`fmt_str` → `5` → `5` > **关键结论**:从右向左压栈本质是**编译器设计的最优解**,它通过固定参数偏移简化编译逻辑,同时与表达式求值顺序协同提升效率[^1][^2]。 --- ### 相关问题 1. 为什么C语言不规定函数参数的求值顺序? 2. 可变参数函数(如 `printf`)如何通过栈定位所有参数? 3. 在x86-64调用约定中,寄存器传参是否仍遵循从右向左顺序? 4. 参数压栈顺序如何影响带有副作用的表达式(如 `a++`)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值