精华内容
下载资源
问答
  • 指针数组 指针数组 指向数组的指针 2.返回指针的函数 3.指向函数的指针 block 4.空类型指针 (通用类型指针) 5.二级指针 ==============================指针数组 一个数组,若其元素均为指针类型数据,则该元素...

    知识点:
    1.指针数组 指针数组 指向数组的指针
    2.返回指针的函数
    3.指向函数的指针 block
    4.空类型指针 (通用类型指针)
    5.二级指针
    ==============================
    指针数组
    一个数组,若其元素均为指针类型数据,则该元素称指针数组,也就是说指针数组中每一个元素都存放一个地址,相当于一个指针变量。

    1.什么是指针数组(指针的集合)
    1)存储指针的数组
    2)所有元素均为指针的数组
    2.本质
    1)指针数组本质仍然为一个数组
    2)每一个存储元素均为指向相同类型的指针
    3.数组声明语法!!!

    类型名 * 数组名[数组长度]

    4.指针数组使用需要注意的问题

    需求:现在程序需要使用100个指针变量

    现在需要一个可以保存n个名字的数组,但是每个人的名字长度不一定相同
    2.用指针数组保存
    学生的姓名
    然后输出。
    练习:
    1、声明一个字符指针数组,分别指向下面字符,然后进行输出;


    2.用指针数组访问 "Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"

    5.main函数的参数
    1)示例:写出完整的main函数定义 int main(int argc,char *argv[ ])
    2)如何在外部给main函数传递启动参数

    ===============================
    返回指针的函数
    一个函数可以返回一个整形值,字符值,实型值,也可以返回指针型数据,即地址。
    其概念与以前类似,只是返回值的类型是指针而已。

    函数的定义: 数据类型 * 函数名 (参数列表)



    1.eg:找到一个字符串中的指定字符,并返回该字符的地址,找不到返回为空。


    2.思考:是否可以返回局部变量的指针
    3.函数可返回指针的类型
    1)通过函数传入的指针
    2) 全局变量


    ==============================
    指向函数的指针 block

    如果在程序中定义一个函数,在编译时,编译系统为函数代码段分配一段存储空间,这段存储空间的起始地址(又称入口地址)称为这个函数的指针。//函数名是函数的入口地址

    可以定义一个指向函数的指针变量,用来存放某一函数的起始地址,这就意味着此指针变量指向该函数。
    需求:用户需要在已有的打印基础上加上自定义的打印格式
    1.作用
    1)调用函数
    2)作为函数参数
    2.如何声明一个指向函数的指针
    //定义函数 类型名 函数名(参数列表)


    定义p是一个指向函数的指针变量,它可以指向函数的类型为整形且有两个整形参数的函数。
    表示。

     

    3.如何使用一个指向函数的指针
    调用一个函数实际上是先找到函数的入口地址。那么调用一个函数有两种方法
    (1)函数名调用
    (2)指向函数的指针变量调用
    //两种方式


    4.如何将一个函数指针通过函数传递

    eg UIButton
    4.如何将参数传递给函数指针
    练习:
    (1)模拟计算器 分别实现求两个数的 最大、最小、加、减* /函数,并用
    函数指针实现。
    ======================
    通用类型指针 void *


    1.作用
    1)可以接收不同数据类型的指针

    2.使用注意
    1)void类型指针不能直接使用,必须转化为对应的类型才可以使用
    2)如果对void指针进行运算,会以一个字节的方式进行运算

    eg: malloc内存申请函数

    NULL void*指针 相互比较

    NULL 空指针
    void * 通用类型指针

    NULL:
    1、初始化指针
    2、可以和任意类型指针进行比较
    if (p != NULL){

    }
    void *
    1、可以和任意类型指针进行转换
    2、任何类型指针都可以赋给void*

    ==========================================
    二级指针


    指向指针的指针
    ==========================================
    二维数组的指针
    int arr[2][3]

    1 2 3
    4 5 6

    二维数组作为参数传递
    示例:显示二维数组所有元素


    练习:1、写一个二维数组的赋值函数。
    int arr[3][3] = {};

    =========================================
    指针其他用法

    1.再论指针赋值
    1)思考:既然指针变量存放地址,那是否可以直接赋值一个整数

    int *p = 5;


    2)指针的相互赋值
    int *p = &a;
    int *q = p;


    2.不同类型指针是相互赋值
    1)思考:不同类型指针间是否可以相互赋值
    2)思考:赋值后是否可以取出正确的数据

    ==========================================


    //指针函数:返回值为指针的函数
    int * funq(int a);
    //函数指针:指向函数的指针
    int (*pf)( int a)


     

     


     

    转载于:https://www.cnblogs.com/iOSteemo/p/4304481.html

    展开全文
  • 这也是笔者最初接触算法时觉得最有意思一点,因为解决问题是熟悉,但配方却完全不同,本章我们从一个简单交集问题出发,一步步认识到多指针及滑动窗口解决某些问题时巧妙与高效,本章主要以解LeetCode里...

    前言

    如果说如何用算法高效有趣的解决某些问题,那多指针和滑动算法绝对是算其中的佼佼者。这也是笔者最初接触算法时觉得最有意思的一点,因为解决的问题是熟悉的,但配方却完全不同,本章我们从一个简单的交集问题出发,一步步的认识到多指针及滑动窗口解决某些问题时的巧妙与高效,本章主要以解LeetCode里高频题为参考~

    多指针

    349 - 两个数组的交集 ↓

    给定两个数组,编写一个函数来计算它们的交集。
    
    输入:nums1 = [1,2,2,1], nums2 = [2,2]
    输出:[2]
    
    输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
    输出:[9,4]
    
    来源:力扣(LeetCode)
    链接:https://leetcode-cn.com/problems/intersection-of-two-arrays

    暴力解:

    将两个数组共有的元素放入一个数组进行去重即可,去重需要使用set,那直接存入set完事。代码如下:

    解法1:
    
    var intersection = function (nums1, nums2) {
      const set = new Set()
      nums1.forEach(num => {
        if (nums2.includes(num)) {
          set.add(num)
        }
      })
      return [...set]
    };

    以下简单假设两个数组的长度都是n,该解法属于暴力解,需要两重的循环,includes需要在数组里查找,所以内部也是遍历,最终的复杂度是O(n²),有没有更高效的解法?

    二分查找:

    当然有,因为是查找问题,我们可以对两个数组分别排序,然后运用上一章我们学习到的二分查找法进行查找,替换includes的查找,那么最终的解法我们能优化到O(nlogn)级别,代码如下:

    解法2:
    
    var intersection = function (nums1, nums2) {
      nums1.sort((a, b) => a - b)
      nums2.sort((a, b) => a - b)
      const set = new Set()
      nums1.forEach(num => {
        if (binarySearch(nums2, num)) {
          set.add(num)
        }
      })
      return [...set]
    };
    
    function binarySearch(arr, target) { // 二分查找
      let l = 0;
      let r = arr.length
      while (r > l) {
        const mid = l + (r - l) / 2 | 0
        if (arr[mid] === target) {
          return true
        } else if (arr[mid] > target) {
          r = mid
        } else {
          l = mid + 1
        }
      }
      return false
    }

    首先对数据进行预处理,最终的代码行数比方法1多了不少,不过总的复杂度是3O(nlogn),比O(n²)快不少,还有更高效的解法么?

    双指针:

    当然,还可以使用一种双指针的解法,首先还是对两个数组进行排序,然后使用两个指针分别指着两个数组的开头,谁的数值小谁向后滑动,遇到相同的元素就放入set内,直至两个数组中有一个到头为止。代码如下:

    解法3:
    
    var intersection = function (nums1, nums2) {
      nums1.sort((a, b) => a - b)
      nums2.sort((a, b) => a - b)
      let i = 0;
      let j = 0;
      const set = new Set()
      while (i < nums1.length && j < nums2.length) { //有一个到头结束循环
        if (nums1[i] === nums2[j]) { // 找到了交集
          set.add(nums1[i]) // 放入set内
          i++
          j++
        } else if (nums1[i] > nums2[j]) { // 谁数值小,+1 移动
          j++
        } else {
          i++
        }
      }
      return [...set]
    };

    整个复杂度需要对两个数组快排,然后双指针要走完两个数组,最终的复杂度是O(nlogn) + O(nlogn) + 2n,虽然总的复杂度还是O(nlogn),不过效率会优于二分查找。

    167 - 两数之和 II - 输入有序数组 ↓

    给定一个已按照升序排列的有序数组,找到两个数使得它们相加之和等于目标数。
    函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
    
    说明:
    返回的下标值(index1 和 index2)不是从零开始的。
    你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
    
    输入: numbers = [2, 7, 11, 15], target = 9
    输出: [1,2]
    解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2。
    
    来源:力扣(LeetCode)
    链接:https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted

    很自然的能想到暴力解,两层循环遍历,最终的复杂度是O(n²),但这不是我们想到的。

    很显然暴力解并没有利用到题目描述里的升序排列这个特性,而最终的结果是需要查找的,所以我们很自然能想到使用二分查找法。这确实也是一种更快的解题思路,能将最终的复杂度降低到O(nlogn)级别,大家可以尝试解决。

    这里提供一种更巧妙的解题思路,对撞指针。我们可以设置头尾两个指针,每一次将它们的和与目标进行比较,如果比目标值大,尾指针向中间移动,减少它们相加的和;反之它们的和如果比目标值小则把头指针向中间移动,增加它们相加的和。因为是有序的数组,所以不用担心移动的过程中错过了目标值。代码如下:

    var twoSum = function (numbers, target) {
      let l = 0 // 左指针
      let r = numbers.length - 1 // 右指针
      while (r > l) { // 不能 r >= l,因为不能使用同一个值两次
        if (numbers[r] + numbers[l] === target) {
          return [l + 1, r + 1]
        } else if (numbers[r] + numbers[l] > target) {
          r-- // 右指针向中间移动
        } else {
          l++ // 左指针向中间移动
        }
      }
      return []
    };

    11 - 盛最多水的容器 ↓

    给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。
    在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。
    找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
    
    示例:
    输入:[1,8,6,2,5,4,8,3,7]
    输出:49 
    解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。
    在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
    
    来源:力扣(LeetCode)
    链接:https://leetcode-cn.com/problems/container-with-most-water

    ac8d78160f6808622283c8faa379e3a7.png

    初看这题可能很懵逼,或者就是使用两层循环的暴力解,求出每种可能,找里里面最大值,面试官对这个解法肯定不会满意。

    而这道经典题目,我们同样可以使用对撞指针解法,首先设置首尾两个指针,依次向中间靠近,但这题麻烦的地方在于两个指针之间谁动谁不动的问题。

    经过观察不难发现,就是指针所指向的值,谁的数值小,谁就需要移动。因为如果数值大的指针向中间移动,小的那个值的指针并不会变,而它们之间的距离会缩短,乘积也会变小。一次遍历即可解决战斗,解题代码如下:

    var maxArea = function (height) {
      let max = 0 // 保存最大的值
      let l = 0;
      let r = height.length - 1
      while (r > l) { // 不能相遇
        const h = Math.min(height[r], height[l])
        max = Math.max(h * (r - l), max) // 容量等于距离差 * 矮的那边条轴
        height[r] > height[l] ? l++ : r-- // 移动矮轴的指针
      }
      return max
    };

    15 - 三数之和 ↓

    给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素a,b,c,使得a+b+c=0?
    请你找出所有满足条件且不重复的三元组。
    注意:答案中不可以包含重复的三元组。
    
    nums = [-1, 0, 1, 2, -1, -4]
    满足要求的三元组集合为:
    [
      [-1, 0, 1],
      [-1, -1, 2]
    ]
    
    来源:力扣(LeetCode)
    链接:https://leetcode-cn.com/problems/3sum

    很容易想到的就是暴力解,使用三层遍历,将三个数字累加和的可能性都计算一遍,提取需要的组合即可,暴力解的复杂度是O(n³)。如果这题是要返回它们对应的下标,那还真没办法,不过既然是返回组合的数字,那我们就可以利用有序数组的特性,还是使用对撞指针更有效率的解决此题。

    7443990eba49a73a49fa9a60ae6778eb.png

    首先对数组进行排序,然后设置三个指针i、l、r,每一轮的循环下标i是固定不动的,让lj对撞,最后根据它们相加的和来移动lr指针。如果和正好等于0,那就找到了一种组合结果;如果大于0,就r--r指针向中间移动;如果小于0,就l++l指针向中间移动,该解法的复杂度是O(n²)。解题代码如下:

    var threeSum = function (nums) {
      nums.sort((a, b) => a - b) // 排序
      const res = []
      for (let i = 0; i < nums.length; i++) {
        let l = i + 1
        let r = nums.length - 1
        if (nums[i] > 0) { // 如果第一个元素就大于0,后面也不用比了
          break;
        }
        if (i > 0 && nums[i] === nums[i - 1]) { // 跳过相同的数字
          continue
        }
        while (r > l) {
          const sum = nums[i] + nums[l] + nums[r];
          if (sum === 0) { // 正好找到
            res.push([nums[i], nums[l], nums[r]])
            while (r > l && nums[l] === nums[l + 1]) { // 跳过相同的数字,一个元素只用一次
              l++
            }
            while (r > l && nums[r] === nums[r - 1]) { // 跳过相同的数字,一个元素只用一次
              r--
            }
            r-- // 缩小范围
            l++ // 缩小范围
          } else if (sum > 0) {
            r-- // 右指针移动,减少下次计算的和
          } else { // sum < 0
            l++ // 左指针移动,增加下次计算的和
          }
        }
      }
      return res
    };

    滑动窗口

    643 - 子数组最大平均数 I ↓

    给定 n 个整数,找出平均数最大且长度为 k 的连续子数组,并输出该最大平均数。
    输入: [1,12,-5,-6,50,3], k = 4
    输出: 12.75
    解释: 最大平均数 (12-5-6+50)/4 = 51/4 = 12.75

    以参数k的长度为一个子数组,所以可以把k看成是一个窗口,在原有数组上进行滑动,每经过一个子数组就求出的它的平均值。如果使用暴力解,会存在重复计算的问题,所以我们每次滑动一步,只需要加上新的元素,然后减去窗口最左侧的元素即可。

    8be5db8a7ee870b39241461e868e4282.png

    解题代码如下:

    var findMaxAverage = function (nums, k) {
      let max = 0
      let sum = 0
      for (let i = 0; i < k; i++) {
        sum += nums[i] // 先求出第一个窗口
      }
      max = sum / k // 第一个窗口的平均值
      let j = k
      while (nums.length > j) {
        sum += nums[j] - nums[j - k] // 加上新元素,减去最左侧元素
        max = Math.max(sum / k, max) // 与之前窗口的平均值比较
        j++ // 向右滑动
      }
      return max // 返回最大窗口平均值
    };

    674 - 最长连续递增序列 ↓

    给定一个未经排序的整数数组,找到最长且连续递增的子序列,并返回该序列的长度。
    
    输入:nums = [1,3,5,4,7]
    输出:3
    解释:最长连续递增序列是 [1,3,5], 长度为3。
    尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为 5 和 7 在原数组里被 4 隔开。
    
    输入:nums = [2,2,2,2,2]
    输出:1
    解释:最长连续递增序列是 [2], 长度为1。
    
    来源:力扣(LeetCode)
    链接:https://leetcode-cn.com/problems/longest-continuous-increasing-subsequence

    这题还是使用滑动窗口解决,为窗口定义两个下标l、r,既然是递增的,那么我们就要两两相邻的进行比较,当遇到的元素大于窗口最右侧值时,将下标l移至r处,重新开始判断统计长度。图示如下:

    ffac68ca6272ddc15bc05a4e57595261.png

    代码如下:

    var findLengthOfLCIS = function (nums) {
      let l = 0;
      let r = 0;
      let max = 0;
      while (nums.length > r) { // 只要窗口还在数组内活动
        if (r > 0 && nums[r - 1] >= nums[r]) { // 如果遇到的元素大于窗口最右侧值
          l = r // 重新统计长度
        }
        max = Math.max(max, r - l + 1) // 统计最长的长度
        r++ // 向右滑动
      }
      return max
    };

    209 - 长度最小的子数组 ↓

    给定一个含有n个正整数的数组和一个正整数s,找出该数组中满足其和≥s的长度最小的连续子数组,并返回其长度。
    如果不存在符合条件的子数组,返回 0。
    
    输入:s = 7, nums = [2,3,1,2,4,3]
    输出:2
    解释:子数组 [4,3] 是该条件下的长度最小的子数组。
    
    来源:力扣(LeetCode)
    链接:https://leetcode-cn.com/problems/minimum-size-subarray-sum

    题目的要求是要找一个连续子数组的和大于或等于传入是s,所以我们还是可以使用滑动窗口,统计窗口内的和,如果已经大于或等于s了,那么此时窗口的长度就是连续子数组的长度。

    当找到一个连续子数组后,让左侧的窗口向右滑动,减去最左侧的值,减小窗口内的和,也让窗口右侧滑动。如果又找到了一个满足条件的子数组,与之前的子数组长度进行比较,更新最小窗口的大小即可。

    6834e56b7ba9b0c64eebf33163aae9bc.png

    有一个特例就是,如果整个数组加起来的和都比s小,那就不能返回窗口的长度,而是直接返回0。 代码如下:

    var minSubArrayLen = function (s, nums) {
      let l = 0
      let r = 0
      let sum = 0 // 窗口里的和
      let size = nums.length + 1 
      // 窗口的大小, 因为是要找到最小的窗口,所以设置一个比最大还 +1 的窗口
      // 如果能找到一个符合条件的子数组才会更新窗口的大小
      while (nums.length > l) { // 让左边界小于整个数组,为了遍历到每一个元素
        if (s > sum) {
          sum += nums[r++] // 窗口和小于s,移动右窗口
        } else {
          sum -= nums[l++] // 窗口大于s,移动左窗口
        }
        if (sum >= s) { // 找到符合的子数组
          size = Math.min(size, r - l) // 更新最小窗口的值
        }
      }
      return size === nums.length + 1 ? 0 : size
      // 如果size等于初始值,表示没有符合要求的子数组,否则有找到
    };

    3 - 无重复字符的最长子串 ↓

    给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
    
    输入: "abcabcbb"
    输出: 3 
    解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
    
    输入: "bbbbb"
    输出: 1
    解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

    这题和上一题类似,滑动窗口不仅仅可以作用于数组,字符串也同样适用。

    这题麻烦一点的地方在于还要定义一个set用于查找,当新加入窗口的元素set里没有时,就加入其中,窗口右移;如果有这个元素,需要将窗口移动到set里出现的位置,也就是在set里将其本身及窗口左侧的元素全部都移除,重复这个过程,直到窗口右侧到达字符串的末尾。如图所示:

    329cd58f9ccfcc6fcbbc9325645bf59b.png

    解题代码如下:

    var lengthOfLongestSubstring = function (s) {
      const set = new Set();
      let l = 0;
      let r = 0;
      let max = 0;
      while (l < s.length && r < s.length) {
        if (!set.has(s[r])) { // 如果查找表里没有
          set.add(s[r++]); // 添加右移窗口
        } else {
          set.delete(s[l++]); 
          // 从左侧开始删除,直到把新加入的且在查找表里有的元素删除为止
          // 然后窗口才会继续开始右滑
        }
        max = Math.max(max, r - l); // 更新最大的值
      }
      return max;
    };

    最后

    以上很多题目也有其他的解法或暴力解,不仅仅局限只有多指针和滑动窗口才能解决,不过在应对难题时,有另一种解题的思路供参考,不过这两种算法对边界的处理能力要求挺高,要特别注意定义指针时开/闭区间的含义。

    想起笔者之前在遇到算法题目之前要么暴力求解,或者就是使用各种遍历api鼓捣一番,当时觉得代码量少还挺好。不过在深入理解了算法之后才明白,代码少不代表效率高,解题的逻辑思维能力才是最重要的。

    展开全文
  • 1 Object内存结构和指针压缩了解一下//hotspotoop.hpp文件中class oopDesc class oopDesc { friend class VMStructs; private: volatile markOop _mark; //对象头部分 union _metadata { // klassOop 类元...

    1 Object的内存结构和指针压缩了解一下

    v2-39a79d9784594c2f15927005c925c87e_b.jpg
    //hotspot的oop.hpp文件中class oopDesc
    class oopDesc {
      friend class VMStructs;
      private:
      volatile markOop  _mark; //对象头部分
      union _metadata {  // klassOop 类元数据指针
        Klass*      _klass;   
        narrowKlass _compressed_klass;
      } _metadata;
    

    Object的实例数据内存使用三部分组成的,对象头实际数据区域内存对齐区

    • 对象头布局如下:主要和锁,hashcode,垃圾回收有关;由于锁机制的内容篇幅过长,这里就不多解释了;和锁相关的markWord(markOop)内存布局如下

    v2-44c0fc52e855bb363d4469a216e16ff0_b.jpg


    • 内存对齐区是什么? HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是对象的大小必须是8字节的整数倍。因此当对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。
    • 内存对齐好处
      • 有利于内存的管理
      • 更快的CPU读取,CPU从内存获取数据,并不是一个个字节的读取,而是按CPU能处理的长度获取,如32位机,是4个字节的内存块;当只需其中两个字节时,则由内存处理器处理挑选。如果需要三个字节分布在两个不同内存块(四字节的内存块),则需要读取内存两次(如果是存在同一内存块只需一次读取)。而当对象按一定的规则合理对齐时,CPU就可以最少地请求内存,加快CPU的执行速度
    • 指针压缩
      • 在上图可以看到,在64位jvm里Object的MarkWord会比32位的大一倍;其实klassOop也扩大一倍占了64位(数组长度部分则是固定四字节)。指针的宽度增大,但是对于堆内存小于4G的,好像也用不到64位的指针。这可以优化吗?答案是就是指针压缩
      • 指针压缩的原理是利用jvm植入压缩指令,进行编码、解码
      • 哪些信息会被压缩?
        • 不被压缩对象:本地变量,堆栈元素,入参,返回值,NULL这些指针
        • 会被压缩对象:类属性、对象头信息、对象引用类型、对象数组类型
      • 指针压缩开启,klassOop大小可以由64bit变成32bit;对象的大小可以看看下面的具体对比
    public static void main(String[] args){
           Object a = new Object(); // 16B   关闭压缩还是16B,需要是8B倍数;12B+填充的4B
           int[] arr = new int[10]; // 24B   关闭压缩则是16B
         }
         public class ObjectNum {
           //8B mark word
           //4B Klass Pointer   如果关闭压缩则占用8B
           //-XX:-UseCompressedClassPointers或-XX:-UseCompressedOops,
           int id;        //4B
           String name;   //4B  如果关闭压缩则占用8B
           byte b;        //1B  实际内存可能会填充到4B
           Object o;      //4B  如果关闭压缩则占用8B
         }
       ```
      • 为什么开启指针压缩时,堆内存最好不要超过32G,指针使用32个bit,为什么最大可使用内存不是4G而是32G?jvm要求对象起始位置对齐8字节的倍数,可以利用这点提升选址范围,理论上可以提升到2^11 * 4G。不过jvm只是将指针左移三位,因此最大范围是2^3 * 4G = 32G。如果大于32G,指针压缩会失效。如果GC堆大小在4G以下,直接砍掉高32位,避免了编码解码过程
      • 启用指针压缩-XX:+UseCompressedOops(默认开启),禁止指针压缩:-XX:-UseCompressedOops

    2 Object的几种基本方法

    • 本地方法
      • private static native void registerNatives() 将Object定义的本地方法和java程序链接起来
      • public final native Class<?> getClass() 获取java的Class元数据
      • public native int hashCode() 获取对象的哈希Code
      • protected native Object clone() throws CloneNotSupportedException获得对象的克隆对象,浅复制
      • public final native void notify() 唤醒等待对象锁waitSet队列中的一个线程
      • public final native void notifyAll() 类似notify(),唤醒等待对象锁waitSet队列中的全部线程
      • public final native void wait(long timeout) 释放对象锁,进入对象锁的waitSet队列
    • 普通方法
    public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode());}
    public boolean equals(Object obj) { return (this == obj);}
    public final void wait(long timeout, int nanos) throws InterruptedException;
    //都是基于native void wait(long timeout)实现的
    public final void wait() throws InterruptedException;
    wait(long timeout, int nanos)wait() 
    //jvm回收对象前,会特意调用此方法 
    protected void finalize() throws Throwable; 

    3 == 、 equals、Comparable.compareTo、Comparator.compara 四种比较方法

    如不指定排序顺序,java里的默认排序顺序是升序的,从小到大

    • ==, (A)对于基本类型之间的比较是值 (B)基本类型和封装类型比较也是值比较 (C)对于引用类型之间的比较则是内存地址
    • equals(Object o), 在Object基本方法里可以看到public boolean equals(Object obj) { return (this == obj);} 是使用 == 去比较的。equals方法的好处是我们可以重写该方法

    Comparable.compareTo 是接口Comparable里的抽象方法;如果对象实现该接口,可使用Collections.sort(List< T> col)进行排序。接下来看看源码怎么实现的Collections.java
    //Collections.sort(List<T> list),调用的是List的sort方法

    public static <T extends Comparable<? super T>> void sort(List<T> list) {
        list.sort(null);
    }
    
    List的sort 则调用了Arrays.sortList.java
    default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
     for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }



    如果Comparator c 为null,则是调用 Arrays.sort(Object[] a) ;最终调用LegacyMergeSort(归并排序)方法处理Arrays.java

    public static <T> void sort(T[] a, Comparator<? super T> c) {
     if (c == null) {
            sort(a);
        } else {
     if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, c);
     else
                TimSort.sort(a, 0, a.length, c, null, 0, 0);
        }
    }
    
    LegacyMergeSort方法里的一段代码最终底层是使用归并排序和compareTo来排序Arrays.java
    ......
     if (length < INSERTIONSORT_THRESHOLD) {
     for (int i=low; i<high; i++)
     for (int j=i; j>low &&
                         ((Comparable) dest[j-1]).compareTo(dest[j])>0; j--)
                    swap(dest, j, j-1);
     return;
        }
    • Comparator也是一个接口,不过提供了更丰富的操作,需要实现int compare(T o1, T o2)方法
      Comparator提供了常用的几个静态方法thenComparing、reversed、reverseOrder(操作对象需要实现Comparator或者Comparable);可配合List.sort、Stream.sorted、Collections.sort使用。
    @Data
    @AllArgsConstructor
    static class Pair implements Comparator<Pair>, Comparable<Pair> {
        Integer one;
        Integer two;
        @Override
        public String toString() { return one + "-" + two; }
        @Override
        public int compareTo(Pair o) { return one.compareTo(o.one);  }
        @Override
        public int compare(Pair o1, Pair o2) {return o1.compareTo(o2);}
    }
    public static void main(String[] args) {
        List<Pair> col = Arrays.asList( new Pair(4, 6), new Pair(4, 2),new Pair(1, 3));
        col.sort(Comparator.reverseOrder());
        col.stream().sorted(Comparator.comparing(Pair::getOne).thenComparing(Pair::getTwo))
                .forEach(item ->  System.out.println(item.toString()) );
    }



    Collections.sort默认是升序排序的,可以看到reverseOrder将顺序反过来了;用了thenComparing的col则是先判断Pair::getOne的大小,如果相等则判断Pair::getTwo大小来排序

    result:
    4-6
    4-2
    1-3
    ----------------
    1-3
    4-2
    4-6

    4 方法的重写和重载

    • 方法的重写是指子类定义和父类方法的名称、参数或者参数顺序一致的方法;需要注意的是,子类重写方法修饰符不能更加严格,就是说父类方法的修饰符是protected,子类不能使用private修饰而可用public,抛出的异常也不能比父类方法定义的更广
    • 方法的重载则是同一个类中定义和已有方法的名称一致而参数或者参数顺序不一致的方法,(返回值不能决定方法的重载)
    • 重载的方法在编译时就可确定(编译时多态),而重写的方法需要在运行时确定(运行时多态,我们常说的多态)
      多态的三个必要条件 1、有继承关系 2、子类重写父类方法 3、父类引用指向子类对象

    5 构造方法是否可被重写

    构造方法是每一个类独有的,并不能被子类继承,因为构造方法没有返回值,子类定义不了和父类的构造方法一样的方法。但是在同一个类中,构造方法可以重载

    public class TestEquals {
        int i;
        public TestEquals() {   i = 0; }
        //构造方法重载
        public TestEquals(int i) {   this.i = i } 
    }

    6 Object的equals和hashCode

    equals是用来比较两个对象是否相等的,可以重写该方法来实现自定义的比较方法;而hashCode则是用来获取对象的哈希值,也可以重写该方法。当对象存储在Map时,是首先利用Object.hashCode判断是否映射在同一位置,若在同一映射位,则再使用equals比较两个对象是否相同。

    7 equals一样,hashCode不一样有什么问题?

    如果重写equals导致对象比较相同而hashCode不一样,是违反JDK规范的;而且当用HashMap存储时,可能会存在多个我们自定义认为相同的对象,这样会为我们代码逻辑埋下坑。

    8 Object.wait和Thread.sheep

    Object.wait是需要在synchronized修饰的代码内使用,会让出CPU,并放弃对对象锁的持有状态。而Thread.sleep则简单的挂起,让出CPU,没有释放任何锁资源

    9 finalize方法的使用

    • 如果对象重写了finalize方法,jvm会把当前对象注册到FinalizerThread的ReferenceQueue队列中。对象没有其他强引用被当垃圾回收时,jvm会判断ReferenceQueue存在该对象,则暂时不回收。之后FinalizerThread(独立于垃圾回收线程)从ReferenceQueue取出该对象,执行自定义的finalize方法,结束之后并从队列移除该对象,以便被下次垃圾回收
    • finalize会造成对象延后回收,可能导致内存溢出,慎用
    • finally和finalize区别
      • finally是java关键字,用来处理异常的,和try搭配使用
      • 如果在finally之前return,finally的代码块会执行吗?
        try内的continue,break,return都不能绕过finally代码块的执行,try结束之后finally是一定会被执行的
    • 相似的关键字final
      • final修饰类,该类不能被继承;修饰方法,方法不能被重写;修饰变量,变量不能指向新的值;修饰数组,数组引用不能指向新数组,但是数组元素可以更改
      • 如果对象被final修饰,变量有哪几种声明赋值方式?
      • fianl修饰普通变量:1、定义时声明 2、类内代码块声明 3、构造器声明
      • fianl修饰静态变量:1、定义时声明 2、类内静态代码块声明

    10 创建对象有哪几种方法

    • 1、使用new创建
    • 2、运用反射获取Class,在newInstance()
    • 3、调用对象的clone()方法
    • 4、通过反序列化得到,如:ObjectInputStream.readObject()

    11 猜猜创建对象的数量

    • String one = new String("Hello");
      两个对象和一个栈变量:一个栈变量one和一个new String()实例对象、一个"hello"字符串对象

    v2-93af2972a6b3b1b1d8b75dccd5863313_b.jpg
    • 题外话:string.intern();intern先判断常量池是否存相同字符串,存在则返回该引用;否则在常量池中记录堆中首次出现该字符串的引用,并返回该引用。
      如果是先执行 String s = "hello" ;相当于执行了intern();先在常量池创建"hello",并且将引用A存入常量池,返回给s。此时String("hello").intern()会返回常量池的引用A返回
        String one = "hello";
        String two = new String("hello");
        String three = one.intern();
        System.out.println(two == one);
        System.out.println(three == one);
        
        result:
        false  // one虽然不等于two;但是它们具体的char[] value 还是指向同一块内存的
        true  // one 和 three 引用相同
    

    v2-07b4f651435d3c30b8c1b5467f8c8428_b.jpg

    12 对象拷贝问题

    • 引用对象的赋值复制是复制的引用对象,A a = new A(); A b = a;此时a和b指向同一块内存的对象

    使用Object.clone()方法,如果字段是值类型(基本类型)则是复制该值,如果是引用类型则复制对象的引用而并非对象

    @Getter
    static class A implements Cloneable{
        private B b; 
        private int index;
        public A(){
            b = new B(); index = 1000;
        }
        public A clone()throws CloneNotSupportedException{  return (A)super.clone(); }
    }
    static class B{
    }
    public static void main(String[] args) throws Exception{
        A a = new A();
        A copyA = a.clone();
        System.out.println( a.getIndex() == copyA.getIndex() );
        System.out.println( a.getB() == copyA.getB() );
    }


    //返回结果都是true,引用类型只是复制了引用值
    true
    true
    • 深复制:重写clone方法时使用序列化复制,(注意需要实现Cloneable,Serializable)
    public A clone() throws CloneNotSupportedException {
            try {
                ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
                ObjectOutputStream out = new ObjectOutputStream(byteOut);
                out.writeObject(this);
                ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
                ObjectInputStream inputStream = new ObjectInputStream(byteIn);
     return (A) inputStream.readObject();
            } catch (Exception e) {
                e.printStackTrace();
                throw new CloneNotSupportedException(e.getLocalizedMessage());
            }
        }

    参考文章


    原作者:csc
    原文链接:基础篇:Object对象
    原出处:公众号- 潜行前行
    侵删

    v2-4d4cccb2b9aae3b11741e93b2f876ed0_b.gif

    展开全文
  • 作业十二

    2016-12-13 18:15:00
    字符串和字符指针:如果定义一个字符指针接收字符串常量值,该指针就指向字符串首字符。这样,字符数组和字符指针都可以用来处理字符串。 字符数组与字符指针都可以处理字符串,但两者之间有重要区别。 char ...

    一。知识点:

     

    字符串和字符指针:如果定义一个字符指针接收字符串常量的值,该指针就指向字符串的首字符。这样,字符数组和字符指针都可以用来处理字符串。

    字符数组与字符指针都可以处理字符串,但两者之间有重要区别。

    char sa[]=''this is a ttring";

    char *sp="this is astring";

    定义自古指针后,如果没有对它赋值,指针的值是不确定的,不能明确它指向的内存单元。因此,如果引用未赋值的指针,可能会出现难以预料的结果。例如:

    char *;

    scanf(“%s“,s);

    为尽量避免引用为赋值的指针所造成的危害,在定义指针时,可先将它的初值为空,如char *s=NULL;

    二。常用的字符串处理函数

    字符串的输入和输出:函数scanf()和gets()可用来输入字符串,而printf()和puts()输出字符串。它们在系统文件stdio.h中定义。

    1.scanf:格式控制字符串中使用格式控制说明%s,输入参数必须是字符型组名。该函数遇回车或空格输入结束,并自动将输入的数据和字符串结束符‘\0’送入数组中.

    2.printf:格式控制字符串中相应的格式控制说明用%s,输出参数可以是字符数组名或字符串常量。输出‘\0’结束。例如:printf(“%s”,s);

    3.字符串输入函数gets(s):参数s是字符数组名。函数从输入得到一个字符串,遇回车输入结束,自动将输入的数据和‘\0’送入数组中。采用函数gets()输入的字符串允许带空格。实际上函数gets()有返回值,如果输入成功则返回值是字符串第一个字符的地址,如果输入失败则返回NULL。但一般情况下使用gets()主要是为了输入字符串,而不关心它的返回值.

    4.字符串输出函数puts(s):参数s可以是字符数组名或字符串常量。输出时遇‘\0’自动将其转换为‘\n’,即输出字符串后换行。同样函数puts()也有返回值,如果成功执行了输出字符串的操作,则返回换行符号‘\n’,否则返回EOF.

    字符串比较函数strcmp(s1,s2)

    和函数strcpy()中对参数的要求不同,函数strcmp()中的参数s1和s2可以是字符数组名或字符串常量。函数strcmp()返回一个整数,给出字符串s1和s2的比较结果:

    若s1和s2相等,返回0。

    若s1大于s2,返回一个正数。

    若s1小于s2,则返回一个负数。

     三。结构的概念与定义:

    结构是C语言中一种新的构造数据类型,它能够把有内在联系的不同类型的数据汇聚成一个整体,使它们相互关联;同时,结构又是一个变量的集合,可以按照对基本数据类型的操作方法单独使用其成员变量。

    结构的嵌套定义:

    在定义嵌套的结构类型时,必须先定义成员的结构类型,再定义主结构类型。

    结构变量的定义和初始化:

    1)单独定义:先定义结构类型,再定义具有这种结构类型的变量;

    2)混合定义:在定义结构类型的同时定义结构体变量;

    struct 结构名{

    类型名 结构成员名1;

    类型名 结构成员2;

    }结构变量名表;

    3)无类型名定义:在定义结构体变量时省略结构体名;

     

    struct{

    类型名 结构成员1;

    类型名 结构成员2;

    结构变量名表;

     

    1.找出最长的字符串:输入5个字符串,输出其中最长的字符串。

    #include<stdio.h>
    #include<string.h>
    int main()
    {
    	int i;
    	printf("输入五个字符串:");
    	char sx[80],smax[80];
    	scanf("%s",sx);
    	strcpy(smax,sx);
    	for(i=1;i<5;i++){
    		scanf("%s",sx);
    		if(strcmp(smax,sx)<0)
    		strcpy(smax,sx);
    		}
    		
    	printf("max is %s\n",smax);
    	return 0;		
    }
    

      

    2.分类统计字符个数:输入一行文字,统计其中的大写字母、小写字母、空格、数字以及其他字符各有多少。

    #include"stdio.h" 
    int main()
    {
        int  a,dx=0,xx=0,kg=0,sz=0,qt=0;
        while((a=getchar())!='\n')
    
        {
            if(a>='A'&&a<='Z')
                dx++;
            else if(a>='a'&&a<='z')
                xx++;
            else if(a==' ')
                kg++;
            else if(a>='0'&&a<='9')
                 sz++;
             else
                 qt++;
            
        }
        printf("大写字母有:%d\n",dx);
        printf("小写字母有:%d\n",xx);
        printf("空格有:%d\n",kg);
        printf("数字有:%d\n",sz);
        printf("其他字符有:%d\n",qt);
    
        return 0;    
    }

    3. 学生信息管理系统。

    假设学生的基本信息包括学号、姓名,三门课程成绩以及个人平均成绩,且最多需要处理50名学生的数据,实现如下功能。

    (1)输入学生基本信息

    (2)输出学生基本信息

    (3)计算每个学生的平均成绩

    (4)按姓名查找学生基本信息

    #include"stdio.h"
    #include"string.h"
    struct student_list{
        int num;
        char name[50];
        int computer,english,math;
        double average;
    };
    int count = 0;
    void new_student(struct student_list students[] );
    void search_student(struct student_list students[],char *name);
    void print_student(struct student_list students[]);
    int main()
    
    {    
        int choice; char name[50];
        struct student_list students[50];
        do{
            printf("******学生的基本信息******\n"); 
            printf("\t1:新建\n");
            printf("\t2:查询\n");
            printf("\t3:输出\n");
            printf("\t0:退出\n");
            printf("请选择功能:");
            scanf("%d",&choice);
            switch(choice){
             case 1: 
                 new_student(students); 
                break;
            case 2:
                printf("请输入要查找的学生姓名:");
                scanf("%s",name);    
                search_student(students,name);
                break;
            case 3:print_student(students);
                break;
            case 0:
                break;
            }
        }while(choice !=0);
        printf("谢谢!\n");
    
        return 0;
    }
    void print_student(struct student_list students[])
    {
        printf("学生信息列表的学生信息如下:\n");
        int i;
        for(i=0;i<count;i++)
        {
            printf("%s    %d %d %d %.2lf %d\n",students[i].name,students[i].computer,students[i].english,students[i].math,students[i].average,students[i].num);
        }
    }
    void new_student(struct student_list students[])
    {
        struct student_list f;
        if(count ==50){
            printf("信息表已满!\n");
            return;    
        }
        printf("请输入新学生的姓名:");
        scanf("%s",f.name);
        printf("请输入新学生的学号:");
        scanf("%d",&f.num);
        printf("请输入新新学生的计算机成绩:");
        scanf("%d",&f.computer);
        printf("请输入新新学生的英语成绩:");
        scanf("%d",&f.math);
        printf("请输入新新学生的数学成绩:");
        scanf("%d",&f.english);
        students[count]=f;
        count++;
    }
    void search_student(struct student_list students[],char*name)
    {
        int i,flag=0;
        if(count ==0){
            printf("学生信息列表是空的\n");
            return;
        }
        for(i=0;i<count;i++)
        if(strcmp(name,students[i].name)==0){
            flag=1;
                break;
        }
        if(flag){
            printf("姓名:%s\t",students[i].name);
            printf("计算机成绩:%d\t",students[i].computer);
            printf("数学成绩:%d\t",students[i].math);
            printf("英语成绩:%d\t",students[i].english);
            printf("平均成绩:%.2lf\t",students[i].average=(students[i].computer+students[i].math+students[i].english)/3.0);                    
            printf("学号:%d\n",students[i].num);    
        }
        else 
        printf("无此学生信息");
    }

    二。实验心得

    这次作业真的好难,应用了许许多多的知识点。在应用的同时,也出现了不少的错误,经过同学的帮助,解决了问题。

     

    转载于:https://www.cnblogs.com/jiejunxu/p/6170992.html

    展开全文
  • Java语言的十二大特色

    2015-03-09 17:02:27
    例如,Java没有算符过载、标题文件、预处理、指针运算、结构、联合、多维数组、模板及隐式类型变换。如果你知道一点C、C++或Pascal,你很快就会驾驭Java。这里是一个简单JavaHelloWorld程序:     ...
  • 链表:链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成,分为以下三种: ...
  • 作业十二 总结

    2016-12-12 11:47:00
    一、知识 1.字符串复制、连接...: (1)字符串复制:strcpy(str1,str2)...2.如果定义一个字符指针接收字符串常量值,该指针就指向字符串首字符。这样,字符数组和字符指针都可以用来处理字符串。例如: ...
  • 例如,Java没有算符过载、标题文件、预处理、指针运算、结构、联合、多维数组、模板及隐式类型变换。如果你知道一点C、C++或Pascal,你很快就会驾驭Java。这里是一个简单JavaHelloWorld程序: ...
  • 例如,Java没有算符过载、标题文件、预处理、指针运算、结构、联合、多维数组、模板及隐式类型变换。如果你知道一点C、C++或Pascal,你很快就会驾驭Java。这里是一个简单JavaHelloWorld程序:
  • 例如,Java没有算符过载、标题文件、预处理、指针运算、结构、联合、多维数组、模板及隐式类型变换。如果你知道一点C、C++或Pascal,你很快就会驾驭Java。这里是一个简单JavaHelloWorld程序:以下是引用片段:...
  • 例如,Java没有算符过载、标题文件、预处理、指针运算、结构、联合、多维数组、模板及隐式类型变换。如果你知道一点C、C++或Pascal,你很快就会驾驭Java。这里是一个简单 JavaHelloWorld程序:以下是引用片段:...
  • 例如,Java没有算符过载、标题文件、预处理、指针运算、结构、联合、多维数组、模板及隐式类型变换。如果你知道一点C、C++或Pascal,你很快就会驾驭Java。这里是一个简单JavaHelloWorld程序:...
  • 例如,Java没有算符过载、标题文件、预处理、指针运算、结构、联合、多维数组、模板及隐式类型变换。如果你知道一点C、C++或Pascal,你很快就会驾驭Java。这里是一个简单 JavaHelloWorld程序: 
  • 例如,Java没有算符过载、标题文件、预处理、指针运算、结构、联合、多维数组、模板及隐式类型变换。如果你知道一点C、C++或Pascal,你很快就会驾驭Java。这里是一个简单JavaHelloWorld程序: public class ...
  • 《C++大学教程》学习笔记(十二) 1.多态性 1.1简介 C++ 支持多态性。所谓多态性是指由于继承而关联在一起不同对象,对于相同...通过基类指针或引用调用虚函数,在执行时进行动态绑定(迟绑定...
  • 忽略点十二:main函数参数 ​ 大家都知道,main函数完整格式如下: int main(int argc, char *argv[]) { // ... return 0; } ​ 这两个参数有什么用呢? ​ main函数第一个参数是整数型,代表命令行...
  • 为了使Scale时候controller上slider不会远离指针,于是把controller固定到了左下角。   在网上找了半天也没有找到好在图像上画球方法,没办法只好用image来当求了,放上球照片即可。得把三维坐标转到UI...
  • Java十二种特色!

    2011-07-26 09:07:36
    1、Java是简单 Java与C++极为相似,但却简单得多。...例如,Java没有算符过载、标题文件、预处 理、指针运算、结构、联合、多维数组、模板及隐式类型变换。如果你知道一点C、C++或Pascal,你很快就会驾驭Java
  • 掌握JAVA编程语言十二大特色和避免十大误区! Java语言12个特色 1.Java是简单  Java与C++极为相似,但却简单得多。 高级编程语言所有特性中,不是绝对需要都已删去了。 例如,Java没有算符过载...
  • (1)一个对象,如果它类有多个基类则有多个虚函数表指针(注意是两个虚函数表指针,而不是两个虚函数表); 在多继承中,对应各个基类vptr按继承顺序依次放置在类内存空间中,且子类与第一个基类共用一个vptr...
  • 例如,Java没有算符过载、标题文件、预处理、指针运算、结构、联合、多维数组、模板及隐式类型变换。假如你知道一点C、C++或Pascal,你很快就会驾驭Java。这里是一个简单JavaHelloWorld程序: public class ...
  • 前言Java用了一年多,Python用了一年多,回过头来和C、C++基础知识都搞混了,最近...目录一~十一:琐碎知识点十二指针十三:结构体一、优先级顺序(自上至下依次降低):!(逻辑运算符-逻辑非) 前置++ 前置-- 强制...
  • 前言Java用了一年多,Python用了一年多,回过头来和C、C++基础知识都搞混了,最近...目录一~十一:琐碎知识点十二指针十三:结构体一、优先级顺序(自上至下依次降低):!(逻辑运算符-逻辑非) 前置++ 前置-- 强制...
  • 先判断两个链表是否有环(两种方法,辅助hashset或用快慢指针),找到入环 两个都无环,判断相交(两种方法,辅助hashse或计算链表长度差N然后长链表先走N步,然后一起走,相遇即为交点) 两个都有环(一个有...
  • C++知识4

    2018-03-03 20:59:34
    继C++知识3十一.下标和指针int ia[4] = {};表达式*(ia + 3)计算ia前进4个元素后的新地址, 解引用该结果指针的效果等价于表达式ia[3].很多情况下,使用数组的名字其实用的就是一个指向数组首元素的指针,例如当...
  • 5.6. 箭头操作符 C++ 语言为包含操作符和解引用操作符的表达式提供了一个同义词:箭头操作符(->)。操作符(第 1.5.2 节)用于获取类类型对象的成员: ...如果有一个指向 Sales_item 对象的指针(或迭
  • C/C++基础知识面试题

    万次阅读 多人点赞 2018-07-26 00:19:44
    三、指针与引用区别? 四、指针与数据区别? 五、不用临时变量实现两个变量交换 七、一个C++源文件从文本到可执行文件经历过程 八、C++11新特性 九、C++和C不同 十、malloc原理 十一、内存泄漏、...

空空如也

空空如也

1 2 3
收藏数 50
精华内容 20
关键字:

十二点的指针