您的位置:首页 > 文旅 > 旅游 > 新媒体运营怎么自学_常德网站建设哪家快_chatgpt 链接_百度seo外链推广教程

新媒体运营怎么自学_常德网站建设哪家快_chatgpt 链接_百度seo外链推广教程

2025/4/6 5:44:13 来源:https://blog.csdn.net/cherryc_/article/details/147013503  浏览:    关键词:新媒体运营怎么自学_常德网站建设哪家快_chatgpt 链接_百度seo外链推广教程
新媒体运营怎么自学_常德网站建设哪家快_chatgpt 链接_百度seo外链推广教程

本专题主要为观看韩顺平老师《零基础30天学会Java》课程笔记,同时也会阅读其他书籍、学习其他视频课程进行学习笔记总结。如有雷同,不是巧合!

有时使用单独定义变量解决/数组存储,不利于数据的管理,并且效率低;数据类型无法直接体现,只能通过下标获取,造成变量名字和内容对应关系不明确。

一、类和对象

  • 类是自定义数据类型,具有属性和方法→对象是类的一个具体实例

  • ⭐对象在内存中的存在形式:

    • 🐈‍⬛栈、堆、方法区:

      在计算机内存管理中,栈(Stack)、堆(Heap)和方法区(Method Area) 是三个核心的内存区域,各自承担不同的职责。以下是它们的详细说明:

      1. 栈(Stack)

      特点

      • 后进先出(LIFO):最后进入的数据最先被移除。
      • 线程私有:每个线程都有自己的栈,互不干扰。
      • 存储局部变量和方法调用
        • 方法调用时,会创建一个栈帧(Stack Frame),存储:
          • 局部变量(基本类型、对象引用)
          • 操作数栈(用于计算)
          • 方法返回地址
        • 方法执行完毕后,栈帧自动弹出(内存自动回收)。
      • 内存分配和回收速度快(由编译器自动管理)。
      • 内存较小(通常几MB,可调整JVM参数)。

      示例

      void foo() {int a = 10;       // 局部变量,存储在栈String s = "hi";  // 引用变量(s在栈,"hi"在堆)
      }
      

      2. 堆(Heap)

      特点

      • 存储对象实例new 创建的对象)。
      • 线程共享:所有线程共用堆内存。
      • 动态分配:内存大小不固定,可扩展(受物理内存限制)。
      • 垃圾回收(GC)管理:不再使用的对象由GC自动回收。
      • 内存较大(默认占JVM内存的大部分)。

      堆内存结构(以JVM为例)

      1. 新生代(Young Generation)
        • Eden区(新对象首先分配到这里)
        • Survivor区(From/To,用于存活对象复制)
      2. 老年代(Old Generation)
        • 长期存活的对象晋升到这里
      3. 元空间(Metaspace,JDK8+) / 永久代(PermGen,JDK7-)
        • 存储类元数据、常量池等(不属于堆,但逻辑相关)

      示例

      String str = new String("Hello");  // "Hello"对象在堆,str引用在栈
      

      3. 方法区(Method Area):存储rug静态的、与类相关的数据

      • 存储类信息
        • 类结构(字段、方法、构造器)
        • 运行时常量池(字符串常量、static final 变量)
        • JIT编译后的代码
        • 静态变量(static修饰的变量)
      • 线程共享:所有线程共用方法区。
      • 逻辑上属于堆,但具体实现因JVM版本不同:
        • JDK7及之前:称为 永久代(PermGen),在堆中。
        • JDK8+:改为 元空间(Metaspace),使用本地内存(不受JVM堆大小限制)。
      • 垃圾回收较少:主要回收无用的类信息。

      示例

      class MyClass {         // 类信息存储在方法区static int x = 10;  // 静态变量在方法区final String s = "ABC";  // 常量在方法区的运行时常量池
      }
      

      三者的关系

      内存区域存储内容线程安全管理方式生命周期
      局部变量、方法调用线程私有自动分配/释放方法执行结束即销毁
      对象实例、数组线程共享GC回收对象不再被引用时回收
      方法区类信息、常量池线程共享GC(部分)JVM关闭时释放

      总结

      1. :存储方法调用和局部变量,速度快但容量小。
      2. :存储所有对象,由GC管理,容量大但访问稍慢。
      3. 方法区:存储类元数据和常量,JDK8+后改为元空间(本地内存)
      • ⚠若类的一个属性为数组,则一个实例的数组的实际数据仍存储在堆中:
        • 数组是动态创建的对象,所有的对象实例都存储在堆内存。数组对象包括:数组长度、元素数据。在运行时通过 new 关键字动态创建(如 new int[10]),其大小和生命周期在编译期无法确定。而方法区存储的是类加载时确定的静态数据(如类信息)。因此数组不符合方法区的存储目标,因为数组的大小和内容可能随时变化,无法静态分配。并且数组可能占用较大内存(如一个 int[100000]),但方法区通常较小(默认几十MB),且扩展成本高(元空间使用本地内存,但仍有上限);而堆可以动态扩容(受物理内存限制)
        • 即使数组被声明为 static final数组对象(实际数据)仍在堆中,只有它的引用存储在方法区(作为静态变量)。
        • 数组的引用(即变量名)的存储位置取决于变量的声明位置:
          • 实例变量(类的成员属性):引用存储在堆内存中(因为对象实例本身在堆中)。
          • 局部变量(方法内的变量):引用存储在栈内存(Stack)中。
  • 属性/成员变量/field(字段):类的一个组成部分,一般是基本数据类型,也可以是引用类型(对象、数组等)

    • 定义语法:访问修饰符 属性类型 属性名;
      • 访问修饰符用于控制属性的访问范围,包括:publicprotected,默认,private
    • 属性如果不赋值,会有默认值,规则和数组一致,即:int-0, short-0, byte-0, long-0, float-0.0, double-0.0, char-\u0000, boolean-false, String-null。‼️给对象分配内存空间后才有默认值。
  • 成员方法/方法:

    • 优点:提高代码的复用性;可用将实现的细节封装起来,供其他用户调用
    • 定义方法:访问修饰符 返回数据类型 方法名(形参列表) {// 方法体}
    public class Method {public static void main(String[] args) {Person p = new Person();p.speak();int sum1 = p.cal01(100)int sum2 = p.cal02(100, 200)}
    }class Person {String name;int age;public void speak() {System.out.println("我是人");}public int cal01(int n) {int sum = 0;for (int i = 1; i <= n; i++)   sum += i;return sum;}public int cal02(int m, int n) {return m + n;}
    }
    

    ‼️一个方法最多有一个返回值。如果想返回多个值,可以返回一个数组。

    ‼️方法不能嵌套定义,即方法体中不能再定义方法。因为Java的变量和方法作用域是类级别的,而非方法级别的。嵌套方法会导致作用域规则混乱。

    • 🐈‍⬛方法嵌套的替代方案:

      (1) 使用Lambda表达式(Java 8+)

      Lambda可以模拟“嵌套方法”的行为,尤其在需要函数式接口的地方:

      void outerMethod() {Runnable nestedAction = () -> {System.out.println("嵌套逻辑(Lambda实现)");};nestedAction.run(); // 调用
      }
      

      (2) 通过内部类/跨类调用类似

      定义一个方法内的局部内部类,包含“嵌套方法”:

      void outerMethod() {class Nested {void nestedMethod() {System.out.println("嵌套逻辑");}}new Nested().nestedMethod(); // 调用
      }
      

      (3) 方法分离到同一类中(调用同一个类的其他方法)

      将逻辑拆分为类的独立方法,通过参数传递数据:

      void outerMethod() {int result = nestedHelper(10); // 调用辅助方法System.out.println(result);
      }private int nestedHelper(int param) { // 辅助方法return param * 2;
      }
      

      (4) 匿名内部类

      适用于接口或抽象类的快速实现:

      void outerMethod() {new Object() {void nestedMethod() {System.out.println("匿名内部类中的嵌套逻辑");}}.nestedMethod(); // 立即调用
      }
      

      为什么其他语言(如Python、JavaScript)支持方法嵌套?

      • 函数式编程特性:这些语言允许函数作为一等公民,嵌套函数可以捕获外部作用域的变量(闭包)。
      • 动态类型系统:无需严格定义类结构,嵌套函数更灵活。

      何时需要“方法嵌套”?如何选择替代方案?

      场景推荐替代方案示例
      简单逻辑复用Lambda表达式() -> System.out.println()
      复杂逻辑封装内部类或独立方法局部内部类或私有辅助方法
      回调或事件处理匿名内部类new Runnable() { run()... }
      需要访问外部方法局部变量Lambda(要求变量为final或等效不可变)int x=10; () -> System.out.println(x)
    • 🐈‍⬛方法无法独立存在:

      在 Java 中,方法不能单独定义在 public 类的外部。Java 的语法规则要求所有方法必须定义在类的内部。

      1. Java 的语法规则

      • 方法必须属于某个类:Java 是纯粹的面向对象语言,所有方法(包括 main)都必须定义在类或接口中。
      • 不能有“游离”方法:类似 C/C++ 的全局函数在 Java 中是不允许的。

      ❌ 错误示例

      // 错误!方法不能定义在类外部
      public void freeMethod() {System.out.println("This is illegal in Java!");
      }public class MyClass {public static void main(String[] args) {freeMethod(); // 编译报错:找不到符号}
      }

      2. 如何实现类似功能?

      (1) 将方法放入类中

      public class MyClass {// 正确:方法定义在类内部public static void freeMethod() {System.out.println("This is legal!");}public static void main(String[] args) {freeMethod(); // 直接调用}
      }
      

      (2) 通过对象调用实例方法

      public class MyClass {// 实例方法public void freeMethod() {System.out.println("Call via object");}public static void main(String[] args) {MyClass obj = new MyClass();obj.freeMethod(); // 通过对象调用}
      }
      

      (3) 使用工具类(静态方法)

      public class Helper {// 工具类的静态方法public static void freeMethod() {System.out.println("Helper method");}
      }public class MyClass {public static void main(String[] args) {Helper.freeMethod(); // 通过类名调用}
      }
      

      3. 为什么 Java 不允许外部方法?

      • 面向对象设计:Java 强制用类封装数据和行为,保持代码结构清晰。
      • 避免命名冲突:类作为命名空间,可以防止全局方法名污染。
      • 封装性:通过类的访问控制(如 public/private)管理方法可见性。

      4. 其他语言的对比

      语言是否支持游离方法替代方案
      Java类中的静态或实例方法
      C++全局函数或命名空间
      Python模块级函数
      C#静态类或实例方法

    🗒️方法定义时的参数称为形式参数(形参);方法调用时传入的参数称为实际参数(实参)。实参和形参的类型要一致或兼容,个数、顺序必须一致。

    🗒️对于形参是基本数据类型时,进行值拷贝,形参的任何改变不影响实参。引用类型传递的是地址,可以通过形参改变实参。

  • 💻克隆对象

    编写一个方法,可以克隆对象,返回复制的对象。要求得到的新对象和原来的对象是两个独立的对象,但属性相同。

    public class CopyObject {public static void main(String[] args) {Person p = new Person();p.name = "Cherry";p.age = 20;p.verify = new int[]{1, 2, 3, 4};Tools tool = new Tools();Person new_p = tool.copyPerson(p);System.out.println("新对象的姓名为:" + new_p.name + ",年龄为:" + new_p.age);System.out.print("新对象的验证数组为:");for (int i = 0; i < new_p.verify.length; i++){System.out. print(new_p.verify[i] + " ");}System.out.println();new_p.name = "Stars";new_p.age = 25;new_p.verify[0] = 10;System.out.println("修改后新对象的姓名为:" + new_p.name + ",年龄为:" + new_p.age);System.out.print("修改后新对象的验证数组为:");for (int i = 0; i < new_p.verify.length; i++){System.out. print(new_p.verify[i] + " ");}System.out.println();System.out.println("修改后原来对象的姓名为:" + p.name + ",年龄为:" + p.age);System.out.print("修改后原来对象的验证数组为:");for (int i = 0; i < p.verify.length; i++){System.out. print(p.verify[i] + " ");}System.out.println();}
    }class Person {String name;int age;int[] verify;
    }class Tools {public Person copyPerson (Person p) {Person new_p = new Person();new_p.name = p.name;new_p.age = p.age;new_p.verify = p.verify;   // 传递数组的地址;否则可以重新new一个数组并拷贝return new_p;}
    }
    

    • 🐈‍⬛String 和 数组 同样作为引用类型,表现不同的原因

      在Java中,String和数组虽然都是引用数据类型,但它们在修改时的行为差异源于不可变性(Immutability)和引用指向的对象是否可变

      1. String的不可变性

      (1) String是不可变类

      • 任何对String的修改(如拼接、替换)都会创建一个新的String对象,而原对象不变。

      • 示例:

        String a = "Hello";
        String b = a;  // b和a指向同一个对象 "Hello"
        b = b + " World"; // 创建新对象 "Hello World",b指向新对象,a仍指向"Hello"
        
        • a 仍为 "Hello"b 变为 "Hello World"

      (2) 内存变化

      堆内存:
      1. "Hello" (a和b最初指向这里)
      2. "Hello World" (b修改后指向这里)
      

      2. 数组的可变性

      (1) 数组是可变对象

      • 数组的元素可以直接修改,不创建新对象

      • 示例:

        int[] arr1 = {1, 2, 3};
        int[] arr2 = arr1; // arr2和arr1指向同一个数组对象
        arr2[0] = 99;     // 修改数组元素,arr1[0]也会变为99
        
        • arr1[0]arr2[0] 都变为 99

      (2) 内存变化

      堆内存:
      1. [1, 2, 3] (arr1和arr2指向这里)
      修改后:
      1. [99, 2, 3] (arr1和arr2仍指向这里)
      

      3. 关键区别总结

      特性String数组
      可变性不可变(修改生成新对象)可变(可直接修改元素)
      赋值行为b = a → 指向同一对象arr2 = arr1 → 指向同一对象
      修改后的影响b 修改后指向新对象,a 不变arr2 修改元素,arr1 同步变化
      内存分配每次修改生成新对象始终操作同一对象

      4. 如何避免数组的“同步修改”问题?

      如果需要独立副本,需显式复制数组:

      int[] arr1 = {1, 2, 3};
      int[] arr2 = arr1.clone(); // 或 Arrays.copyOf(arr1, arr1.length),并且导入类:java.util.Arrays,因为这是一个静态方法,必须通过类名调用
      arr2[0] = 99; // arr1不受影响
      

二、递归

  • 定义:方法自己调用自己,并且每次调用时传入不同的变量。

  • 递归机制分析案例:

  • 递归的重要规则:

  • 💻猴子吃桃

    有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个。以后每天猴子都吃其中的一半,然后再多吃一个。当到第10天时,想再吃时 (即还没吃)发现只有1个桃子了。问题:最初共多少个桃子?【逆推思想】

    import java.util.Scanner;public class Recursion {public static void main(String[] args) {System.out.print("查询第几天的桃子数?:");Scanner scanner = new Scanner(System.in);int day = scanner.nextInt();int remains = eat_peach(day);if (remains == -1)System.out.println("输入天数错误!");elseSystem.out.println("第 " + day + " 天剩余 " + remains + " 个桃子");}public static int eat_peach(int day) {if (day == 10) return 1;else if (day >= 1 && day < 10) return (eat_peach(day + 1) + 1) * 2;else return -1;}
    }
    

  • 💻迷宫问题

    用二维数组表示迷宫,0表示没有障碍物,1表示有障碍物;

    使用递归回溯,递归出口是最后一个坐标被标记为可以走通,或当前位置向任何方向移动都是死路(回溯,即返回给上一层递归False,然后上一个位置继续向其他方向探测);

    寻找路径时需要标记:2-已经走过,可以走 3-已经走过,但是死路;

    目前先规定寻找顺序:下→右→上→左,如果探测的位置是0则可以下一步;

    探测到一个位置时,先假定可以走通,并按顺序探测,如果四个方向都不能到终点,则重置为不能走通

    import java.util.Scanner;public class Recursion {public static void main(String[] args) {// 用二维数组表示迷宫int[][] map = new int[8][7];for (int j = 0; j < 7; j++) {map[0][j] = 1;map[7][j] = 1;}for (int i = 1; i < 7; i++) {map[i][0] = 1;map[i][6] = 1;}map[2][2] = map[3][1] = map[3][2] = 1;// 输出原始迷宫System.out.println("=======迷宫地图=======");for (int i = 0; i < map.length; i++) {for (int j = 0; j < map[i].length; j++) {System.out.print(map[i][j] + " ");}System.out.println();}// 开始探测find_way(map, 1, 1);// 输出探测后标记的路径System.out.println("=======探测路径=======");for (int i = 0; i < map.length; i++) {for (int j = 0; j < map[i].length; j++) {System.out.print(map[i][j] + " ");}System.out.println();}}// 从map[i][j]开始查找路径public static boolean find_way(int[][] map, int i, int j) {if (map[6][5] == 2) {return true;}else {if (map[i][j] == 0) {// 没有被探测过map[i][j] = 2;   // 先假设可以走通// 下->右->上->左if (find_way(map, i + 1, j))	return true;else if (find_way(map, i, j + 1))	return true;else if (find_way(map, i - 1, j))	return true;else if (find_way(map, i, j - 1))	return true;else {map[i][j] = 3;	// 重置为不能走通return false;}}else {return false;}}}
    }
    

  • 💻汉诺塔问题

    有三个柱子,第一个柱子从小到大堆了一些盘子,要求把第一个柱子的盘子移动到第三个柱子,保持大小不变,并且每次只能移动一个盘子。【将最后一个盘子上面的所有盘子看成一个整体,如果只有两个盘子的话,将小盘子移到中间,再将大盘子放到第三个,最后把小盘子放到第三个大盘子上】

    import java.util.Scanner;public class Recursion {public static void main(String[] args) {System.out.print("输入盘子的个数:");Scanner scanner = new Scanner(System.in);int num = scanner.nextInt();	// 盘子个数move_plate(num, 'A', 'B', 'C');}public static void move_plate(int num, char a, char b, char c) {// a,b,c代表三根柱子的编号,a为当前在的柱子,c为目标柱子if (num == 1) {// 递归出口System.out.println(a + "->" + c);}else {// 把上面的移到b,最后一个移到c,再把上面的移到cmove_plate(num - 1, a, c, b);System.out.println(a + "->" + c);move_plate(num - 1, b, a, c);}}
    }
    
  • 💻⭐八皇后

    在8*8的棋盘上摆放8个皇后,使其不能相互攻击,即:任意两个皇后不能处于同一行、同一列、同一斜线,有几种摆法?【用一维数组存储皇后的位置,下标为行,数值为该行皇后放置的列】

    实现思路:

    1. 逐行放置皇后
      • 每一行放一个皇后,避免行冲突。
    2. 检查列和对角线冲突
      • 列冲突:当前列是否已有皇后。
      • 对角线冲突:两个皇后是否在同一个主对角线(行差 = 列差)或副对角线(行差 = -列差)。
    3. 递归回溯
      • 如果当前行的某个位置可以放皇后,则递归处理下一行。
      • 如果无法放置,则回溯到上一行,尝试其他位置。
    public class Recursion {private static final int N = 8; // 棋盘大小// static:表示变量属于类,而非对象(所有对象共享同一份数据)// final 是一个关键字,用于表示 不可变性;如果修饰引用类型(如对象、数组),则引用不可变(但对象内部状态可能可变,例如数组元素可以修改)private static int[] queens = new int[N]; // queens[i] = j 表示第i行的皇后放在第j列private static int count = 0; // 记录解法总数public static void main(String[] args) {solve(0); // 从第0行开始放置System.out.println("Total solutions: " + count);}/*** 递归放置皇后* @param row 当前要放置的行*/private static void solve(int row) {if (row == N) { // 所有行都已放置,找到一个解printSolution();count++;return;}for (int col = 0; col < N; col++) {		// 从第一列开始判断if (isSafe(row, col)) { // 检查当前位置是否安全queens[row] = col; // 放置皇后solve(row + 1);    // 递归处理下一行}}}/*** 检查 (row, col) 是否可以放置皇后*/private static boolean isSafe(int row, int col) {for (int i = 0; i < row; i++) {// 检查列冲突或对角线冲突if (queens[i] == col || Math.abs(row - i) == Math.abs(col - queens[i])) {return false;}}return true;}/** 打印当前解法 */private static void printSolution() {System.out.println("Solution " + count + ":");for (int i = 0; i < N; i++) {for (int j = 0; j < N; j++) {System.out.print(queens[i] == j ? "Q " : ". ");}System.out.println();}System.out.println();}
    }
    

    其他结果省略…

三、重载

  • 定义:Java中允许同一个类中,存在多个同名方法,但是要求形参列表不一致(参数类型/个数/顺序,至少有一个不同;与修饰符、返回类型无关)。例如System.out.println()可以输出多个类型数据,因为outPrintStream类型的一个实例。利于接口编程,减轻起名负担。

可变参数

Java允许将同一个类中多个同名、同功能,但是**参数个数(0个~任意个)**不同的方法,封装成一个方法。使用可变参数时,可以当作数组使用。

访问修饰符 返回类型 方法名(数据类型... 形参名) {
}
  • 可变参数的实参可以直接是数组

  • 本质是数组

  • 可一个普通类型的参数一起放在形参列表,但是可变参数必须放在最后

  • 一个形参列表中只能有一个可变参数

  • 🐈‍⬛编程语言中的可变参数

    可变参数(Variable Arguments,简称 varargs)是一种允许函数接受不定数量参数的特性。以下是主流编程语言中的支持情况和使用方法:


    1. Java

    语法

    使用 类型... 参数名 声明可变参数,必须是方法的最后一个参数

    public void printValues(String... values) {for (String s : values) {System.out.println(s);}
    }
    // 调用
    printValues("A", "B", "C"); // 可传任意数量参数
    

    特点

    • 编译后转为数组(String[])。
    • 可以传递数组:printValues(new String[]{"A", "B"})

    2. Python

    语法

    使用 *args 接收可变位置参数,**kwargs 接收可变关键字参数。

    def print_values(*args, **kwargs):for arg in args:  # 处理位置参数print(arg)for key, value in kwargs.items():  # 处理关键字参数print(f"{key}: {value}")
    # 调用
    print_values(1, 2, 3, name="Alice", age=25)
    

    特点

    • args 将参数打包为元组(tuple)。
    • *kwargs 将关键字参数打包为字典(dict)。

    3. JavaScript

    语法

    使用 arguments 对象或剩余参数(...rest)。

    // 传统方式(arguments)
    function printValues() {for (let i = 0; i < arguments.length; i++) {console.log(arguments[i]);}
    }// ES6剩余参数
    function printValues(...values) {values.forEach(v => console.log(v));
    }
    // 调用
    printValues("A", "B", "C");
    

    特点

    • arguments 是类数组对象(非真正的数组)。
    • ...rest 将参数转为真正的数组(支持数组方法如 mapfilter)。

    4. C/C++

    语法

    C 使用 <stdarg.h> 宏,C++11 支持模板和 initializer_list

    // C语言(需固定至少一个参数)
    #include <stdarg.h>
    void printValues(int count, ...) {va_list args;va_start(args, count);for (int i = 0; i < count; i++) {int value = va_arg(args, int);printf("%d\\\\n", value);}va_end(args);
    }
    // 调用
    printValues(3, 10, 20, 30);
    

    C++实现案例:

    1. 使用 C++11 变参模板(推荐)

    变参模板是类型安全的,适合现代 C++ 开发。

    示例:打印不定数量的参数

    #include <iostream>
    using namespace std;// 基础情况:递归终止
    void printArgs() {cout << endl;  // 参数包为空时换行
    }// 递归展开参数包
    template<typename T, typename... Args>
    void printArgs(T first, Args... rest) {cout << first << " ";  // 打印第一个参数printArgs(rest...);    // 递归处理剩余参数
    }int main() {printArgs(1, 3.14, "Hello", 'A');  // 输出: 1 3.14 Hello Areturn 0;
    }
    
    • typename... Args:声明变参模板。
    • Args... rest:解包参数包。
    • 递归终止条件:无参数的 printArgs()

    2. 使用 std::initializer_list(同类型参数)

    适用于参数类型相同的情况(如全部为 int)。

    示例:计算整数和

    #include <iostream>
    #include <initializer_list>
    using namespace std;int sum(initializer_list<int> nums) {int total = 0;for (auto num : nums) {total += num;}return total;
    }int main() {cout << sum({1, 2, 3, 4});  // 输出: 10return 0;
    }
    

    3. C 风格 va_list(不推荐,仅兼容旧代码)

    C++ 兼容 C 的可变参数机制,但缺乏类型安全

    示例:打印不定数量参数

    #include <iostream>
    #include <cstdarg>
    using namespace std;void printValues(int count, ...) {va_list args;va_start(args, count);  // 初始化 va_listfor (int i = 0; i < count; i++) {int value = va_arg(args, int);  // 按 int 类型提取参数cout << value << " ";}va_end(args);  // 清理 va_list
    }int main() {printValues(3, 10, 20, 30);  // 输出: 10 20 30return 0;
    }
    
    • 需手动指定参数类型(如 va_arg(args, int))。
    • 无法自动检测参数类型错误(如传递 double 但按 int 读取)。

    4. 变参模板进阶:完美转发(C++11)

    结合 std::forward 实现参数完美转发。

    示例:构造对象

    #include <iostream>
    #include <string>
    using namespace std;class Person {
    public:Person(const string& name, int age) : name(name), age(age) {cout << "Constructed: " << name << ", " << age << endl;}
    private:string name;int age;
    };template<typename... Args>
    void createPerson(Args&&... args) {Person person(std::forward<Args>(args)...);  // 完美转发参数
    }int main() {createPerson("Alice", 25);  // 输出: Constructed: Alice, 25return 0;
    }
    
    方法类型安全适用场景复杂度
    变参模板任意类型、现代 C++高(需递归)
    std::initializer_list同类型参数
    va_list兼容 C 代码中(易出错)
    完美转发构造函数/函数参数转发

    特点

    • C 中需手动管理参数类型和数量(易出错)。
    • C++ 更推荐使用 std::initializer_list 或模板包(template <typename... Args>)。

    5. C#

    语法

    使用 params 关键字。

    public void PrintValues(params string[] values) {foreach (string s in values) {Console.WriteLine(s);}
    }
    // 调用
    PrintValues("A", "B", "C");
    

    特点

    • 类似 Java,编译后转为数组。
    • 必须是参数列表的最后一个。

    6. Ruby

    语法

    使用 *args 接收可变参数。

    def print_values(*values)values.each { |v| puts v }
    end
    # 调用
    print_values("A", "B", "C")
    

    特点

    • values 将参数转为数组(Array 类型)。

    7. Go

    语法

    使用 ... 前缀表示可变参数。

    func printValues(values ...int) {for _, v := range values {fmt.Println(v)}
    }
    // 调用
    printValues(1, 2, 3)       // 传多个值
    printValues([]int{4, 5}...) // 传切片需解包
    

    特点

    • 可变参数必须是同一类型。
    • 函数内部视为切片(slice)。

    8. Swift

    语法

    使用 values: Int...

    func printValues(_ values: Int...) {for v in values {print(v)}
    }
    // 调用
    printValues(1, 2, 3)
    

    特点

    • 参数类型必须明确指定(如 Int...)。
    • 函数内部当作数组([Int])使用。

    对比总结
    语言语法内部类型限制
    JavaString... values数组必须是最后一个参数
    Python*args, **kwargs元组/字典
    JS...values数组
    Cva_list手动解析需固定首个参数
    C#params string[]数组必须是最后一个参数
    Ruby*values数组
    Govalues ...int切片必须同类型
    Swiftvalues: Int...数组必须指定类型

    使用建议

    1. 安全性:优先选择类型安全的实现(如 Java/C# 的 params,Go/Swift 的类型约束)。
    2. 灵活性:Python/JS 的 args*kwargs 适合动态场景。
    3. 性能:C/C++ 的 va_list 需谨慎使用(易引发未定义行为)。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com