• Android Camera详解


    相关的类

    1. android.hardware.camera2
    2. Camera
    3. SurfaceView---这个类用于向用户呈现实时相机预览。
    4. MediaRecorder---这个类用于从摄像机录制视频。
    5. Intent---MediaStore.ACTION_IMAGE_CAPTURE或MediaStore.ACTION_VIDEO_CAPTURE可用于捕获图像或视频,而无需直接使用Camera对象。

    清单声明

    在使用Camera API开始开发应用程序之前,应确保您的清单具有适当的声明,以允许使用相机硬件和其他相关功能。

    • 相机权限 - 您的应用程序必须请求使用设备相机的权限。

      <uses-permission android:name="android.permission.CAMERA" />

      注意:如果您通过调用现有的摄像头应用程序来使用摄像头,则应用程序不需要请求此权限。

    • 相机功能 - 您的应用程序还必须声明使用相机功能,例如:

      <uses-feature android:name="android.hardware.camera" />

    • 存储权限 - 如果应用程序将图像或视频保存到设备的外部存储设备(SD卡),则还必须在清单中指定此选项。

      <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    • 音频录制权限 - 对于使用视频捕获录制音频,应用程序必须请求获取音频捕获权限。

      <uses-permission android:name="android.permission.RECORD_AUDIO" />

    创建自定义摄像头

    为应用程序创建自定义摄像头界面的一般步骤如下:

    1. 检测和访问摄像机 - 创建代码以检查摄像机是否存在并请求访问。
    2. 创建预览类 - 创建扩展SurfaceView并实现SurfaceHolder界面的相机预览类。这个类预览来自相机的实时图像。
    3. 构建预览布局 - 一旦你拥有相机预览类,创建一个包含预览和你想要的用户界面控件的视图布局。
    4. Capture的监听器 - 为您的接口控件连接侦听器,以响应用户操作(例如按下按钮)开始捕获图像或视频。
    5. 捕获和保存文件 - 设置用于捕获图片或视频和保存输出的代码。
    6. 释放相机 - 使用相机后,应用程序必须正确释放它以供其他应用程序使用。

    相机硬件是一个共享资源,必须仔细管理,以便您的应用程序不会与其他可能也想使用它的应用程序冲突。以下部分讨论如何检测相机硬件,如何请求访问相机,如何捕获图片或视频以及如何在应用程序使用完成后释放相机。

    检测相机硬件

    如果您的应用程序没有特别要求使用清单声明的摄像机,则应检查相机在运行时是否可用。要执行此检查,请使用PackageManager.hasSystemFeature()方法,如下面的示例代码所示:

    /** Check if this device has a camera */
    private boolean checkCameraHardware(Context context) {
        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
            // this device has a camera
            return true;
        } else {
            // no camera on this device
            return false;
        }
    }

    Android设备可以具有多个摄像机,例如用于摄影的背面摄像机和用于视频通话的前置摄像机。Android 2.3(API Level 9)及更高版本允许您使用Camera.getNumberOfCameras()方法检查设备上可用的摄像机数量。

    访问相机

    如果您确定运行应用程序的设备具有摄像头,则必须通过获取摄像头实例来请求访问它(除非您正在使用意图来访问一个摄像头)。
    要访问主要摄像机,请使用Camera.open()方法,并确保捕获任何异常,如下面的代码所示:

    /** A safe way to get an instance of the Camera object. */
    public static Camera getCameraInstance(){
        Camera c = null;
        try {
            c = Camera.open(); // attempt to get a Camera instance
        }
        catch (Exception e){
            // Camera is not available (in use or does not exist)
        }
        return c; // returns null if camera is unavailable
    }

    注意:使用Camera.open()时始终检查异常。如果相机正在使用或不存在,则无法检查异常,将导致您的应用程序被系统关闭。

    在运行Android 2.3(API Level 9)或更高版本的设备上,您可以使用Camera.open(int)访问特定的摄像机。上面的示例代码将访问具有多个摄像头的设备上的第一个后置摄像头

    检查相机功能

    获取对摄像机的访问权限后,您可以使用Camera.getParameters()方法获取有关其功能的更多信息,并检查返回的Camera.Parameters对象以获取支持的功能。使用API​​ Level 9或更高版本时,请使用Camera.getCameraInfo()确定相机是位于设备的正面还是背面,以及图像的方向。

    创建预览类

    为了让用户有效地拍摄照片或视频,他们必须能够看到设备相机看到的内容。相机预览类是SurfaceView,可以显示来自相机的实时图像数据,因此用户可以框架并捕获图片或视频。
    以下示例代码演示了如何创建可包含在视图布局中的基本摄像机预览类。这个类实现了SurfaceHolder.Callback,以便捕获用于创建和销毁视图的回调事件,这是分配摄像机预览输入所需要的。

    /** A basic Camera preview class */
    public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
        private SurfaceHolder mHolder;
        private Camera mCamera;
    
        public CameraPreview(Context context, Camera camera) {
            super(context);
            mCamera = camera;
    
            // Install a SurfaceHolder.Callback so we get notified when the
            // underlying surface is created and destroyed.
            mHolder = getHolder();
            mHolder.addCallback(this);
            // deprecated setting, but required on Android versions prior to 3.0
            mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        }
    
        public void surfaceCreated(SurfaceHolder holder) {
            // The Surface has been created, now tell the camera where to draw the preview.
            try {
                mCamera.setPreviewDisplay(holder);
                mCamera.startPreview();
            } catch (IOException e) {
                Log.d(TAG, "Error setting camera preview: " + e.getMessage());
            }
        }
    
        public void surfaceDestroyed(SurfaceHolder holder) {
            // empty. Take care of releasing the Camera preview in your activity.
        }
    
        public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
            // If your preview can change or rotate, take care of those events here.
            // Make sure to stop the preview before resizing or reformatting it.
    
            if (mHolder.getSurface() == null){
              // preview surface does not exist
              return;
            }
    
            // stop preview before making changes
            try {
                mCamera.stopPreview();
            } catch (Exception e){
              // ignore: tried to stop a non-existent preview
            }
    
            // set preview size and make any resize, rotate or
            // reformatting changes here
    
            // start preview with new settings
            try {
                mCamera.setPreviewDisplay(mHolder);
                mCamera.startPreview();
    
            } catch (Exception e){
                Log.d(TAG, "Error starting camera preview: " + e.getMessage());
            }
        }
    }

    如果要为相机预览设置特定大小,请在上述注释中所述的surfaceChanged()方法中设置此大小。设置预览大小时,必须使用getSupportedPreviewSizes()的值。不要在setPreviewSize()方法中设置任意值。

    注意:随着Android 7.0(API级别24)及更高版本中多窗口功能的引入,即使在调用setDisplayOrientation()之后,也不能再假设预览的纵横比与您的活动相同。根据窗口大小和宽高比,您可能必须使用信箱布局将宽相机预览适配为纵向布局,反之亦然。

    在布局中放置预览

    相机预览类(例如上一部分中所示的示例)必须与用于拍摄图片或视频的其他用户界面控件一起放置在活动的布局中。

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        >
      <FrameLayout
        android:id="@+id/camera_preview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        />
    
      <Button
        android:id="@+id/button_capture"
        android:text="Capture"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        />
    </LinearLayout>

    在大多数设备上,相机预览的默认方向为横向。此示例布局指定水平(横向)布局,以下代码将应用程序的方向固定为横向。为了简化渲染相机预览,您应该将以下内容添加到清单中,以将应用程序的预览活动方向更改为横向。

    <activity android:name=".CameraActivity"
              android:label="@string/app_name"
    
              android:screenOrientation="landscape">
              <!-- configure this activity to use landscape orientation -->
    
              <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    注意:相机预览不必处于横向模式。从Android 2.2(API级别8)开始,您可以使用setDisplayOrientation()方法设置预览图像的旋转。为了在用户重新定位手机时更改预览方向,请在预览类的surfaceChanged()方法中,首先使用Camera.stopPreview()停止预览,更改方向,然后再次使用Camera.startPreview()启动预览。

    在相机视图的活动中,将预览类添加到上面示例中所示的FrameLayout元素。您的相机活动还必须确保在暂停或关闭时释放相机。以下示例显示如何修改相机活动以附加创建预览类中显示的预览类。

    public class CameraActivity extends Activity {
    
        private Camera mCamera;
        private CameraPreview mPreview;
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
    
            // Create an instance of Camera
            mCamera = getCameraInstance();
    
            // Create our Preview view and set it as the content of our activity.
            mPreview = new CameraPreview(this, mCamera);
            FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
            preview.addView(mPreview);
        }
    }

    注意:上面示例中的getCameraInstance()方法指的是访问摄像机中显示的示例方法。

    捕获图片

    一旦构建了预览类和显示它的视图布局,就可以开始使用应用程序捕获图像。在应用程序代码中,您必须为用户界面控件设置侦听器,以通过拍摄图片来响应用户操作。在应用程序代码中,您必须为用户界面控件设置侦听器,以通过拍摄图片来响应用户操作。

    为了检索图片,请使用Camera.takePicture()方法。此方法需要三个参数,从相机接收数据。为了接收JPEG格式的数据,您必须实现一个Camera.PictureCallback接口来接收图像数据并将其写入文件。以下代码显示了Camera.PictureCallback接口的基本实现,用于保存从相机接收的图像。

    private PictureCallback mPicture = new PictureCallback() {
    
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
    
            File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE);
            if (pictureFile == null){
                Log.d(TAG, "Error creating media file, check storage permissions: " +
                    e.getMessage());
                return;
            }
    
            try {
                FileOutputStream fos = new FileOutputStream(pictureFile);
                fos.write(data);
                fos.close();
            } catch (FileNotFoundException e) {
                Log.d(TAG, "File not found: " + e.getMessage());
            } catch (IOException e) {
                Log.d(TAG, "Error accessing file: " + e.getMessage());
            }
        }
    };

    通过调用Camera.takePicture()方法触发捕获图像。以下示例代码显示了如何从按钮View.OnClickListener调用此方法。

    // Add a listener to the Capture button
    Button captureButton = (Button) findViewById(id.button_capture);
    captureButton.setOnClickListener(
        new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // get an image from the camera
                mCamera.takePicture(null, null, mPicture);
            }
        }
    );

    警告:记住,当您的应用程序使用它时,通过调用Camera.release()来释放Camera对象!有关如何释放相机的信息,请参阅释放相机。

    捕获视频

    使用Android框架的视频捕获需要仔细管理Camera对象和与MediaRecorder类的协调。使用Camera录制视频时,除了Camera.open()和Camera.release()调用外,还必须管理Camera.lock()和Camera.unlock()调用,以允许MediaRecorder访问摄像机硬件。

    注意:从Android 4.0(API级别14)开始,Camera.lock()和Camera.unlock()调用会自动管理。

    与使用设备相机拍摄照片不同,捕获视频需要非常特殊的通话顺序。您必须遵循特定的执行顺序,才能使用您的应用程序成功准备和捕获视频,如下所述。

    1. 打开相机 - 使用Camera.open()获取相机对象的实例。
    2. 连接预览 - 通过使用Camera.setPreviewDisplay()将SurfaceView连接到摄像机,准备实时的摄像机图像预览
    3. 开始预览 - 调用Camera.startPreview()开始显示实时摄像机图像。
    4. 开始录制视频 - 必须完成以下步骤才能成功录制视频:
      1. 解锁相机 - 通过调用Camera.unlock()解锁相机以供MediaRecorder使用。
      2. 配置MediaRecorder - 按以下顺序调用以下MediaRecorder方法。有关更多信息,请参阅MediaRecorder参考文档。
        1. setCamera() - 设置要用于视频捕获的摄像机,使用应用程序的当前摄像机实例。
        2. setAudioSource() - 设置音频源,使用MediaRecorder.AudioSource.CAMCORDER。
        3. setVideoSource() - 设置视频源,使用MediaRecorder.VideoSource.CAMERA。
        4. 设置视频输出格式和编码。对于Android 2.2(API Level 8)及更高版本,请使用MediaRecorder.setProfile方法,并使用CamcorderProfile.get()获取配置文件实例。对于2.2之前的Android版本,必须设置视频输出格式和编码参数:
          1. setOutputFormat() - 设置输出格式,指定默认设置或MediaRecorder.OutputFormat.MPEG_4
          2. setAudioEncoder() - 设置声音编码类型,指定默认设置或MediaRecorder.AudioEncoder.AMR_NB。
          3. setVideoEncoder() - 设置视频编码类型,指定默认设置或MediaRecorder.VideoEncoder.MPEG_4_SP。
        5. 准备MediaRecorder - 通过调用MediaRecorder.prepare()为提供的配置设置准备MediaRecorder。
        6. 启动MediaRecorder - 通过调用MediaRecorder.start()开始录制视频
    5. 停止录制视频 - 按顺序调用以下方法,以成功完成视频录制:
      1. 停止MediaRecorder - 停止通过调用MediaRecorder.stop()录制视频。
      2. 重置MediaRecorder - (可选)通过调用MediaRecorder.reset()从记录器中删除配置设置。
      3. 释放MediaRecorder - 通过调用MediaRecorder.release()释放MediaRecorder。
      4. 锁定相机 - 锁定相机,以便将来的MediaRecorder会话可以通过调用Camera.lock()来使用它。从Android 4.0(API级别14)开始,不需要此调用,除非MediaRecorder.prepare()调用失败。
    6. 停止预览 - 当您的活动使用相机完成后,使用Camera.stopPreview()停止预览。
    7. 释放相机 - 释放相机,以便其他应用程序可以通过调用Camera.release()来使用它。

    注意:可以先使用MediaRecorder而不创建相机预览,并跳过此过程的前几个步骤。然而,由于用户通常喜欢在开始记录之前看到预览,所以这里不讨论该处理。

    提示:如果您的应用程序通常用于录制视频,请在开始预览之前将setRecordingHint(boolean)设置为true。此设置可以帮助减少开始录制所需的时间。

    配置MediaRecorder

    当使用MediaRecorder类记录视频时,必须按特定顺序执行配置步骤,然后调用MediaRecorder.prepare()方法来检查和实现配置。以下示例代码演示如何正确配置和准备MediaRecorder类进行视频录制。

    private boolean prepareVideoRecorder(){
    
        mCamera = getCameraInstance();
        mMediaRecorder = new MediaRecorder();
    
        // Step 1: Unlock and set camera to MediaRecorder
        mCamera.unlock();
        mMediaRecorder.setCamera(mCamera);
    
        // Step 2: Set sources
        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
    
        // Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
        mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));
    
        // Step 4: Set output file
        mMediaRecorder.setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString());
    
        // Step 5: Set the preview output
        mMediaRecorder.setPreviewDisplay(mPreview.getHolder().getSurface());
    
        // Step 6: Prepare configured MediaRecorder
        try {
            mMediaRecorder.prepare();
        } catch (IllegalStateException e) {
            Log.d(TAG, "IllegalStateException preparing MediaRecorder: " + e.getMessage());
            releaseMediaRecorder();
            return false;
        } catch (IOException e) {
            Log.d(TAG, "IOException preparing MediaRecorder: " + e.getMessage());
            releaseMediaRecorder();
            return false;
        }
        return true;
    }

    在Android 2.2(API级别8)之前,您必须直接设置输出格式和编码格式参数,而不是使用CamcorderProfile。这种方法在下面的代码中演示:

     // Step 3: Set output format and encoding (for versions prior to API Level 8)
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);

    MediaRecorder的以下视频录制参数被赋予默认设置,但是,您可能想要为应用程序调整这些设置:

    setVideoEncodingBitRate()
    setVideoSize()
    setVideoFrameRate()
    setAudioEncodingBitRate()
    setAudioChannels()
    setAudioSamplingRate()

    启动和停止MediaRecorder

    当使用MediaRecorder类启动和停止视频录制时,必须遵循以下列出的特定顺序。

    1. 使用Camera.unlock()解锁相机
    2. 配置MediaRecorder,如上面的代码示例所示
    3. 使用MediaRecorder.start()开始录制
    4. 录制视频
    5. 使用MediaRecorder.stop()停止录制
    6. 使用MediaRecorder.release()释放媒体记录器
    7. 使用Camera.lock()锁定相机

    以下示例代码演示了如何连接一个按钮,以使用相机和MediaRecorder类正确启动和停止视频录制。

    注意:完成视频录制时,请勿释放相机,否则将停止预览。

    private boolean isRecording = false;
    
    // Add a listener to the Capture button
    Button captureButton = (Button) findViewById(id.button_capture);
    captureButton.setOnClickListener(
        new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (isRecording) {
                    // stop recording and release camera
                    mMediaRecorder.stop();  // stop the recording
                    releaseMediaRecorder(); // release the MediaRecorder object
                    mCamera.lock();         // take camera access back from MediaRecorder
    
                    // inform the user that recording has stopped
                    setCaptureButtonText("Capture");
                    isRecording = false;
                } else {
                    // initialize video camera
                    if (prepareVideoRecorder()) {
                        // Camera is available and unlocked, MediaRecorder is prepared,
                        // now you can start recording
                        mMediaRecorder.start();
    
                        // inform the user that recording has started
                        setCaptureButtonText("Stop");
                        isRecording = true;
                    } else {
                        // prepare didn't work, release the camera
                        releaseMediaRecorder();
                        // inform user
                    }
                }
            }
        }
    );

    注意:在上面的示例中,prepareVideoRecorder()方法引用配置MediaRecorder中显示的示例代码。此方法负责锁定相机,配置和准备MediaRecorder实例。(详见例子)

    释放相机

    摄像机是由设备上的应用程序共享的资源。您的应用程序可以在获取相机实例后使用相机,并且您的应用程序停止使用时,以及应用程序暂停后(Activity.onPause()),您必须特别小心释放相机对象。如果应用程序未正确释放相机,则随后尝试访问相机(包括您自己的应用程序)将会失败,并可能导致您的或其他应用程序关闭。

    要释放Camera对象的实例,请使用Camera.release()方法,如下面的示例代码所示。

    public class CameraActivity extends Activity {
        private Camera mCamera;
        private SurfaceView mPreview;
        private MediaRecorder mMediaRecorder;
    
        ...
    
        @Override
        protected void onPause() {
            super.onPause();
            releaseMediaRecorder();       // if you are using MediaRecorder, release it first
            releaseCamera();              // release the camera immediately on pause event
        }
    
        private void releaseMediaRecorder(){
            if (mMediaRecorder != null) {
                mMediaRecorder.reset();   // clear recorder configuration
                mMediaRecorder.release(); // release the recorder object
                mMediaRecorder = null;
                mCamera.lock();           // lock camera for later use
            }
        }
    
        private void releaseCamera(){
            if (mCamera != null){
                mCamera.release();        // release the camera for other applications
                mCamera = null;
            }
        }
    }

    警告:如果您的应用程序未正确释放相机,则随后尝试访问相机(包括您自己的应用程序)将会失败,并可能导致您的或其他应用程序关闭。

    保存媒体文件

    用户创建的媒体文件(如图片和视频)应保存到设备的外部存储目录(SD卡),以节省系统空间,并允许用户在没有设备的情况下访问这些文件。在设备上保存媒体文件有许多可能的目录位置,但是您应该考虑作为开发人员只有两个标准位置:

    1. Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) --此方法返回用于保存图片和视频的标准,共享和推荐的位置。此目录是共享(公共),因此其他应用程序可以轻松地发现,读取,更改和删除保存在此位置的文件。如果用户卸载了您的应用程序,则不会删除保存到此位置的媒体文件。了避免干扰用户现有的图片和视频,您应该为此目录中的应用程序的媒体文件创建一个子目录,如下面的代码示例所示。此方法在Android 2.2(API级别8)中可用,适用于早期API版本中的等效调用,请参阅保存共享文件。
    2. Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) ---此方法返回用于保存与您的应用程序相关联的图片和视频的标准位置。如果您的应用程序已卸载,则会删除保存在此位置的所有文件。不对此位置中的文件强制执行安全性,其他应用程序可能会读取,更改和删除它们。

    以下示例代码演示了如何为媒体文件创建文件或Uri位置,当使用Intent或作为构建相机应用程序的一部分调用设备的摄像机时,可以使用该位置。

    public static final int MEDIA_TYPE_IMAGE = 1;
    public static final int MEDIA_TYPE_VIDEO = 2;
    
    /** Create a file Uri for saving an image or video */
    private static Uri getOutputMediaFileUri(int type){
          return Uri.fromFile(getOutputMediaFile(type));
    }
    
    /** Create a File for saving an image or video */
    private static File getOutputMediaFile(int type){
        // To be safe, you should check that the SDCard is mounted
        // using Environment.getExternalStorageState() before doing this.
    
        File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
                  Environment.DIRECTORY_PICTURES), "MyCameraApp");
        // This location works best if you want the created images to be shared
        // between applications and persist after your app has been uninstalled.
    
        // Create the storage directory if it does not exist
        if (! mediaStorageDir.exists()){
            if (! mediaStorageDir.mkdirs()){
                Log.d("MyCameraApp", "failed to create directory");
                return null;
            }
        }
    
        // Create a media file name
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        File mediaFile;
        if (type == MEDIA_TYPE_IMAGE){
            mediaFile = new File(mediaStorageDir.getPath() + File.separator +
            "IMG_"+ timeStamp + ".jpg");
        } else if(type == MEDIA_TYPE_VIDEO) {
            mediaFile = new File(mediaStorageDir.getPath() + File.separator +
            "VID_"+ timeStamp + ".mp4");
        } else {
            return null;
        }
    
        return mediaFile;
    }

    注意:Environment.getExternalStoragePublicDirectory()在Android 2.2(API级别8)或更高版本中可用。如果您要使用早期版本的Android定位设备,请改用Environment.getExternalStorageDirectory()。有关详细信息,请参阅保存共享文件。

    有关在Android设备上保存文件的详细信息,请参阅数据存储。

    相机功能(用来设置更加详细的视屏,例如各种比例和白平衡等。后期再补充详细)

    Android支持您可以使用相机应用程序控制的各种相机功能,例如图片格式,闪光模式,对焦设置等。本节列出常见的摄像机功能,并简要讨论如何使用它们。大多数摄像机功能可以使用通过Camera.Parameters对象访问和设置。然而,有几个重要的功能,需要的不仅是Camera.Parameters中的简单设置。这些功能将在以下部分中介绍:

    1. 测光和对焦区域
    2. 面部检测
    3. 时间流逝视频
  • 相关阅读:
    副业收入是我做程序员的2倍!副业这么有 “钱”景,我要考虑转行吗?
    C语言丨const关键字的用法详解
    C/C++学习笔记:C/C++函数调用的方式,你应该要学会这五种
    软件崩溃了,该如何解决? 解决问题的关键要会对症下药!
    C语言丨深入理解volatile关键字
    C语言丨getch(),getche()和getchar()的区别
    学编程的误区——眼高手低,不重视练习!
    通过编写“猜测数字”游戏来探索Linux中的Bash
    零基础想要更快入门Linux?找对方法,让你少奋斗10年!
    VS/VC 出现闪退怎么办?这4个技巧要知道!
  • 原文地址:https://www.cnblogs.com/dongweiq/p/6346857.html
Copyright © 2020-2023  润新知